{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.14","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":9819595,"sourceType":"datasetVersion","datasetId":6020780},{"sourceId":9900911,"sourceType":"datasetVersion","datasetId":6082008},{"sourceId":9904809,"sourceType":"datasetVersion","datasetId":6085017},{"sourceId":9907055,"sourceType":"datasetVersion","datasetId":6086690}],"dockerImageVersionId":30787,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-11-14T11:15:41.062904Z","iopub.execute_input":"2024-11-14T11:15:41.063700Z","iopub.status.idle":"2024-11-14T11:15:42.619475Z","shell.execute_reply.started":"2024-11-14T11:15:41.063644Z","shell.execute_reply":"2024-11-14T11:15:42.618461Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 2nd column is valence, 3rd column is arousal \n\n# 31 rows x 4 columns","metadata":{}},{"cell_type":"code","source":"# /kaggle/input/raw-ppg-gsr-data-for-emotion-recognition\n\nsample_arousal_valence = pd.read_csv(\"/kaggle/input/raw-ppg-gsr-data-for-emotion-recognition/raw_data_ppg_gsr/10/10/Arousal_Valence.csv\")\n# sample_arousal_valence = pd.read_csv(\"/kaggle/input/raw-ppg-gsr-data-for-emotion-recognition/raw_data_ppg_gsr/10/10/Arousal_Valence.csv\")\nprint(sample_arousal_valence)","metadata":{"execution":{"iopub.status.busy":"2024-11-14T11:16:43.541344Z","iopub.execute_input":"2024-11-14T11:16:43.541766Z","iopub.status.idle":"2024-11-14T11:16:43.566570Z","shell.execute_reply.started":"2024-11-14T11:16:43.541727Z","shell.execute_reply":"2024-11-14T11:16:43.565456Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sample_arousal_valence.shape","metadata":{"execution":{"iopub.status.busy":"2024-11-13T14:59:38.841113Z","iopub.execute_input":"2024-11-13T14:59:38.841955Z","iopub.status.idle":"2024-11-13T14:59:38.848536Z","shell.execute_reply.started":"2024-11-13T14:59:38.841915Z","shell.execute_reply":"2024-11-13T14:59:38.847595Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# import pandas as pd\n\n# sample_raw_gsr = pd.read_csv('/kaggle/input/raw-ppg-gsr-data-for-emotion-recognition/raw_data_ppg_gsr/10/10/raw_gsr.csv', delimiter=',', encoding='latin1')\n# # print(sample_raw_ppg)\n# pd.read_csv(\"/kaggle/input/raw_data_ppg_gsr/10/10/Arousal_Valence.csv\")\n# with open('/kaggle/input/raw-ppg-gsr-data-for-emotion-recognition/raw_data_ppg_gsr/10/10/raw_gsr.csv', 'r') as file:\nwith open('/kaggle/input/raw_data_ppg_gsr/10/10/raw_gsr.csv', 'r') as file:\n    for _ in range(5):\n        print(file.readline())\n        \n# sample_raw_gsr = pd.read_csv('/kaggle/input/raw-ppg-gsr-data-for-emotion-recognition/raw_data_ppg_gsr/10/10/raw_gsr.csv')","metadata":{"execution":{"iopub.status.busy":"2024-11-13T14:59:41.30326Z","iopub.execute_input":"2024-11-13T14:59:41.304088Z","iopub.status.idle":"2024-11-13T14:59:41.312928Z","shell.execute_reply.started":"2024-11-13T14:59:41.304047Z","shell.execute_reply":"2024-11-13T14:59:41.311947Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# with open('/kaggle/input/raw-ppg-gsr-data-for-emotion-recognition/raw_data_ppg_gsr/10/10/raw_gsr.csv', 'r') as file:\nwith open('/kaggle/input/raw_data_ppg_gsr/10/10/raw_gsr.csv', 'r') as file:\n    for i, line in enumerate(file):\n        if 40 <= i <= 50:  # Print lines near the problematic line\n            print(f\"Line {i}: {line}\")\n","metadata":{"execution":{"iopub.status.busy":"2024-11-13T14:59:47.150315Z","iopub.execute_input":"2024-11-13T14:59:47.151045Z","iopub.status.idle":"2024-11-13T14:59:47.165236Z","shell.execute_reply.started":"2024-11-13T14:59:47.150986Z","shell.execute_reply":"2024-11-13T14:59:47.164298Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"3.\nraw_gsr.csv: This CSV file contains the raw GSR data. For each line, the first item (if not null) represents the timestamp. The second item denotes the GSR data. The third item stores trigger information. Similar to camera.csv, we use k + 10 and k + 100 to indicate the start and stop of the k-th video, respectively.\n\n4.\nraw_ppg.csv: This CSV file contains the raw PPG data. The organization structure of PPG data is the same as GSR data in raw_gsr.csv. The only difference between raw_ppg.csv and raw_gsr.csv is the number of lines per second (100 lines per second for PPG and 4 lines for GSR due to different sampling rates).","metadata":{}},{"cell_type":"code","source":"# with open('/kaggle/input/raw-ppg-gsr-data-for-emotion-recognition/raw_data_ppg_gsr/10/10/raw_gsr.csv', 'r') as file:\nwith open('/kaggle/input/raw_data_ppg_gsr/10/10/raw_gsr.csv', 'r') as file:\n    for _ in range(55):\n        print(file.readline())","metadata":{"execution":{"iopub.status.busy":"2024-11-13T14:59:53.029988Z","iopub.execute_input":"2024-11-13T14:59:53.030752Z","iopub.status.idle":"2024-11-13T14:59:53.038057Z","shell.execute_reply.started":"2024-11-13T14:59:53.030707Z","shell.execute_reply":"2024-11-13T14:59:53.037138Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# for gsr\n\n# sample_raw_gsr = pd.read_csv(\n#     '/kaggle/input/raw-ppg-gsr-data-for-emotion-recognition/raw_data_ppg_gsr/10/10/raw_gsr.csv', \n#     names=['timestamp', 'gsr_value', 'extra_column'],  # Define expected columns\n#     na_values=[''],  # Treat empty strings as NaN\n#     skiprows=1  # Skip header if needed\n# )\n\nsample_raw_gsr = pd.read_csv(\n    '/kaggle/input/raw_data_ppg_gsr/10/10/raw_gsr.csv', \n    names=['timestamp', 'gsr_value', 'start_stop_trigger'],  # Define expected columns\n    na_values=[''],  # Treat empty strings as NaN\n#     skiprows=1  # Skip header if needed\n)","metadata":{"execution":{"iopub.status.busy":"2024-11-13T15:15:26.66999Z","iopub.execute_input":"2024-11-13T15:15:26.67083Z","iopub.status.idle":"2024-11-13T15:15:26.687773Z","shell.execute_reply.started":"2024-11-13T15:15:26.670784Z","shell.execute_reply":"2024-11-13T15:15:26.686862Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Get unique start_stop_triggers\nunique_triggers = sample_raw_gsr['start_stop_trigger'].unique().tolist()\n\n# Find the positions (row numbers) of each unique start_stop_trigger\ntrigger_positions = {trigger: sample_raw_gsr[sample_raw_gsr['start_stop_trigger'] == trigger].index.tolist()\n                     for trigger in unique_triggers}\n\n# Print the results\nprint(\"Unique start_stop_triggers:\", unique_triggers)\nprint(\"Positions of start_stop_triggers:\", trigger_positions)","metadata":{"execution":{"iopub.status.busy":"2024-11-13T15:15:28.652842Z","iopub.execute_input":"2024-11-13T15:15:28.653219Z","iopub.status.idle":"2024-11-13T15:15:28.685084Z","shell.execute_reply.started":"2024-11-13T15:15:28.653182Z","shell.execute_reply":"2024-11-13T15:15:28.683959Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(sample_raw_gsr)","metadata":{"execution":{"iopub.status.busy":"2024-11-13T15:11:28.081909Z","iopub.execute_input":"2024-11-13T15:11:28.082692Z","iopub.status.idle":"2024-11-13T15:11:28.094885Z","shell.execute_reply.started":"2024-11-13T15:11:28.082652Z","shell.execute_reply":"2024-11-13T15:11:28.093867Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# import pandas as pd\n\n# # Load the raw GSR data\n# sample_raw_gsr = pd.read_csv('/kaggle/input/raw_data_ppg_gsr/10/10/raw_gsr.csv',\n#                              names=['timestamp', 'gsr_value', 'start_stop_trigger'],\n#                              na_values=[''])\n\n# # Load the Arousal_Valence ratings\n# arousal_valence_df = pd.read_csv('/kaggle/input/raw_data_ppg_gsr/10/10/Arousal_Valence.csv', \n#                                  names=['video_id', 'valence', 'arousal', 'dominance'])\n\n# # Dictionary to store data intervals for each video ID\n# data_intervals = {}\n\n# for k in range(32):  # video IDs from 0 to 31\n#     # Define start and stop triggers\n#     start_trigger = k + 10\n#     stop_trigger = k + 100\n\n#     # Find the row indices for start and stop triggers\n#     start_index = sample_raw_gsr[sample_raw_gsr['start_stop_trigger'] == start_trigger].index\n#     stop_index = sample_raw_gsr[sample_raw_gsr['start_stop_trigger'] == stop_trigger].index\n\n#     # Ensure start and stop triggers exist and capture the interval\n#     if not start_index.empty and not stop_index.empty:\n#         interval_data = sample_raw_gsr.loc[start_index[0]:stop_index[0], ['timestamp', 'gsr_value']]\n#         data_intervals[k] = interval_data  # Store interval data for video ID k\n\n# # Print or use data_intervals as needed to analyze arousal and valence correlations\n# # Print each interval data for each video ID\n# for video_id, interval_data in data_intervals.items():\n#     print(f\"Video ID: {video_id}\")\n#     print(interval_data)\n#     print(\"\\n\" + \"=\"*50 + \"\\n\")  # Separator between intervals","metadata":{"execution":{"iopub.status.busy":"2024-11-13T15:28:21.71928Z","iopub.execute_input":"2024-11-13T15:28:21.720223Z","iopub.status.idle":"2024-11-13T15:28:21.857855Z","shell.execute_reply.started":"2024-11-13T15:28:21.720168Z","shell.execute_reply":"2024-11-13T15:28:21.856862Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import pandas as pd\n\n# Load the raw GSR data\nsample_raw_gsr = pd.read_csv('/kaggle/input/raw_data_ppg_gsr/10/10/raw_gsr.csv',\n                             names=['timestamp', 'gsr_value', 'start_stop_trigger'],\n                             na_values=[''])\n\n# Load the Arousal_Valence ratings\narousal_valence_df = pd.read_csv('/kaggle/input/raw_data_ppg_gsr/10/10/Arousal_Valence.csv', \n                                 names=['video_id', 'valence', 'arousal', 'dominance'])\n\n# Dictionary to store data intervals with arousal and valence labels for each video ID\ndata_intervals_with_labels = {}\n\nfor k in range(32):  # video IDs from 0 to 31\n    # Define start and stop triggers\n    start_trigger = k + 10\n    stop_trigger = k + 100\n\n    # Find the row indices for start and stop triggers\n    start_index = sample_raw_gsr[sample_raw_gsr['start_stop_trigger'] == start_trigger].index\n    stop_index = sample_raw_gsr[sample_raw_gsr['start_stop_trigger'] == stop_trigger].index\n\n    # Ensure start and stop triggers exist and capture the interval\n    if not start_index.empty and not stop_index.empty:\n        interval_data = sample_raw_gsr.loc[start_index[0]:stop_index[0], ['timestamp', 'gsr_value']]\n\n        # Retrieve the arousal and valence values for the current video ID\n        valence = arousal_valence_df.loc[arousal_valence_df['video_id'] == k, 'valence'].values[0]\n        arousal = arousal_valence_df.loc[arousal_valence_df['video_id'] == k, 'arousal'].values[0]\n\n        # Store interval data along with valence and arousal labels\n        data_intervals_with_labels[k] = {\n            'gsr_data': interval_data,\n            'valence': valence,\n            'arousal': arousal\n        }\n\n# Print the GSR data intervals with their corresponding valence and arousal values\nfor video_id, data in data_intervals_with_labels.items():\n    print(f\"Video ID: {video_id}\")\n    print(\"Valence:\", data['valence'])\n    print(\"Arousal:\", data['arousal'])\n    print(\"GSR Data:\")\n    print(data['gsr_data'])\n    print(\"\\n\" + \"=\"*50 + \"\\n\")  # Separator between intervals\n","metadata":{"execution":{"iopub.status.busy":"2024-11-13T16:08:03.159079Z","iopub.execute_input":"2024-11-13T16:08:03.159874Z","iopub.status.idle":"2024-11-13T16:08:03.332187Z","shell.execute_reply.started":"2024-11-13T16:08:03.159831Z","shell.execute_reply":"2024-11-13T16:08:03.331249Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Use start stop triggers to match data time intervals","metadata":{}},{"cell_type":"code","source":"import pandas as pd\n\n# Load the raw PPG data\nsample_raw_ppg = pd.read_csv('/kaggle/input/raw_data_ppg_gsr/10/10/raw_ppg.csv',\n                             names=['timestamp', 'ppg_value', 'start_stop_trigger'],\n                             na_values=[''])\n\n# Load the Arousal_Valence ratings\narousal_valence_df = pd.read_csv('/kaggle/input/raw_data_ppg_gsr/10/10/Arousal_Valence.csv', \n                                 names=['video_id', 'valence', 'arousal', 'dominance'])\n\n# Dictionary to store data intervals with arousal and valence labels for each video ID\nppg_data_intervals_with_labels = {}\n\nfor k in range(32):  # video IDs from 0 to 31\n    # Define start and stop triggers\n    start_trigger = k + 10\n    stop_trigger = k + 100\n\n    # Find the row indices for start and stop triggers\n    start_index = sample_raw_ppg[sample_raw_ppg['start_stop_trigger'] == start_trigger].index\n    stop_index = sample_raw_ppg[sample_raw_ppg['start_stop_trigger'] == stop_trigger].index\n\n    # Ensure start and stop triggers exist and capture the interval\n    if not start_index.empty and not stop_index.empty:\n        interval_data = sample_raw_ppg.loc[start_index[0]:stop_index[0], ['timestamp', 'ppg_value']]\n\n        # Retrieve the arousal and valence values for the current video ID\n        valence = arousal_valence_df.loc[arousal_valence_df['video_id'] == k, 'valence'].values[0]\n        arousal = arousal_valence_df.loc[arousal_valence_df['video_id'] == k, 'arousal'].values[0]\n\n        # Store interval data along with valence and arousal labels\n        ppg_data_intervals_with_labels[k] = {\n            'ppg_data': interval_data,\n            'valence': valence,\n            'arousal': arousal\n        }\n\n# Print the PPG data intervals with their corresponding valence and arousal values\nfor video_id, data in ppg_data_intervals_with_labels.items():\n    print(f\"Video ID: {video_id}\")\n    print(\"Valence:\", data['valence'])\n    print(\"Arousal:\", data['arousal'])\n    print(\"PPG Data:\")\n    print(data['ppg_data'])\n    print(\"\\n\" + \"=\"*50 + \"\\n\")  # Separator between intervals\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# No need to run this as the data and categories as stored in text file\n# Iterate through all subject folders and obtain their data intervals based on start stop trigger & arousal and valence\n \n# Not normalized - need this for time & frequency domain features extraction, apply standard scaler after train test split","metadata":{}},{"cell_type":"code","source":"import os\nimport pandas as pd\n\nbase_dir = '/kaggle/input/raw-ppg-gsr-data-for-emotion-recognition/raw_data_ppg_gsr/'\ndata_intervals_not_normalized = []\n\n# Function to load and process PPG or GSR data\ndef load_sensor_data(file_path, start_trigger, stop_trigger, value_column):\n    data = pd.read_csv(file_path, names=['timestamp', value_column, 'start_stop_trigger'], na_values=[''])\n    start_index = data[data['start_stop_trigger'] == start_trigger].index\n    stop_index = data[data['start_stop_trigger'] == stop_trigger].index\n    if not start_index.empty and not stop_index.empty:\n        return data.loc[start_index[0]:stop_index[0], value_column].values\n    else:\n        return None\n\n# Mapping function for arousal and valence levels\ndef map_level(value):\n    if 1 <= value <= 3:\n        return 'L'  # Low\n    elif 4 <= value <= 6:\n        return 'M'  # Medium\n    elif 7 <= value <= 9:\n        return 'H'  # High\n    \n# Iterate over each subject's folder\nfor subject_id in os.listdir(base_dir):\n    subject_path = os.path.join(base_dir, subject_id, subject_id)\n    \n    # Check if the folder name is purely numeric (indicating a subject)\n    if os.path.isdir(subject_path) and subject_id.isdigit():\n        \n        # Load arousal and valence labels for this subject\n        arousal_valence_path = os.path.join(subject_path, 'Arousal_Valence.csv')\n        arousal_valence_df = pd.read_csv(arousal_valence_path, names=['video_id', 'valence', 'arousal', 'dominance'])\n        \n        # Paths to GSR and PPG data files\n        gsr_path = os.path.join(subject_path, 'raw_gsr.csv')\n        ppg_path = os.path.join(subject_path, 'raw_ppg.csv')\n\n        # Only process if both GSR and PPG data files exist\n        if os.path.exists(gsr_path) and os.path.exists(ppg_path):\n\n            for k in range(32):  # Loop over each video ID's start/stop triggers\n                start_trigger = k + 10\n                stop_trigger = k + 100\n\n                # Load GSR data interval\n                gsr_interval = load_sensor_data(gsr_path, start_trigger, stop_trigger, 'gsr_value')\n\n                # Load PPG data interval\n                ppg_interval = load_sensor_data(ppg_path, start_trigger, stop_trigger, 'ppg_value')\n\n                # Check if intervals are valid and retrieve labels\n                if gsr_interval is not None and ppg_interval is not None:\n                    valence = arousal_valence_df.loc[arousal_valence_df['video_id'] == k, 'valence'].values[0]\n                    arousal = arousal_valence_df.loc[arousal_valence_df['video_id'] == k, 'arousal'].values[0]\n                    \n                    # Map arousal and valence to Low, Medium, High\n                    arousal_level = map_level(arousal)\n                    valence_level = map_level(valence)\n                    arousal_valence_label = f\"A{arousal_level}V{valence_level}\"\n\n                    # Convert GSR and PPG data to comma-separated strings\n                    gsr_data_str = \",\".join(map(str, gsr_interval))\n                    ppg_data_str = \",\".join(map(str, ppg_interval))\n\n                    # Append data to the intervals list for model input\n                    data_intervals_not_normalized.append({\n                        'subject_id': subject_id,\n                        'video_id': k,\n                        'gsr_data': gsr_data_str,  # Store as comma-separated string\n                        'ppg_data': ppg_data_str,  # Store as comma-separated string\n                        'valence': valence,\n                        'arousal': arousal,\n                        'arousal_level': arousal_level,\n                        'valence_level': valence_level,\n                        'arousal_valence_label': arousal_valence_label  # Store arousal_valence_label\n                    })\n\n# Print a few samples to verify data\nfor data in data_intervals_not_normalized[:5]:  # Print first 5 samples as a check\n    print(f\"Subject ID: {data['subject_id']}, Video ID: {data['video_id']}\")\n    print(\"Valence:\", data['valence'])\n    print(\"Arousal:\", data['arousal'])\n    print(\"GSR Data:\", data['gsr_data'])\n    print(\"PPG Data:\", data['ppg_data'])\n    print(\"Arousal Valence Label:\", data['arousal_valence_label'])  # Print the label\n    print(\"\\n\" + \"=\"*50 + \"\\n\")  # Separator between samples\n","metadata":{"execution":{"iopub.status.busy":"2024-11-14T11:18:07.769740Z","iopub.execute_input":"2024-11-14T11:18:07.770155Z","iopub.status.idle":"2024-11-14T11:22:41.215094Z","shell.execute_reply.started":"2024-11-14T11:18:07.770117Z","shell.execute_reply":"2024-11-14T11:22:41.214025Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# print(len(data))","metadata":{"execution":{"iopub.status.busy":"2024-11-13T17:44:29.72407Z","iopub.execute_input":"2024-11-13T17:44:29.724818Z","iopub.status.idle":"2024-11-13T17:44:29.729654Z","shell.execute_reply.started":"2024-11-13T17:44:29.724761Z","shell.execute_reply":"2024-11-13T17:44:29.728764Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(len(data_intervals_not_normalized))","metadata":{"execution":{"iopub.status.busy":"2024-11-14T11:23:17.711605Z","iopub.execute_input":"2024-11-14T11:23:17.711979Z","iopub.status.idle":"2024-11-14T11:23:17.717407Z","shell.execute_reply.started":"2024-11-14T11:23:17.711945Z","shell.execute_reply":"2024-11-14T11:23:17.716498Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Save to text file and csv file","metadata":{}},{"cell_type":"code","source":"\nimport os\nimport pandas as pd\n\n\n# Define a path to save the files in the working directory\noutput_dir = '/kaggle/working/'\n\n# Ensure the output directory exists\nos.makedirs(output_dir, exist_ok=True)\n\n# Save the data_intervals to a TXT file (saving as a readable string format)\ndef save_to_txt(data_intervals, filename='data_intervals_not_normalized1.txt'):\n    file_path = os.path.join(output_dir, filename)\n    with open(file_path, 'w') as f:\n        for item in data_intervals:\n            f.write(str(item) + \"\\n\\n\")  # Each dictionary entry on a new line with a space between them\n    print(f\"Data saved to {file_path}\")\n\n# Save the data_intervals to text format\n\nsave_to_txt(data_intervals_not_normalized)\n\n\nprint(\"Data saved to .txt file in Kaggle working directory.\")\n\n\n\n# Convert the data_intervals list of dictionaries to a DataFrame\ndata_intervals_dataframe = pd.DataFrame(data_intervals_not_normalized)\n\n# Save the DataFrame to a CSV file\ndata_intervals_dataframe.to_csv('data_intervals_not_normalized1.csv', index=False)\n\nprint(\"Data saved to data_intervals_not_normalized1.csv\")\n\n# Specify the path to the CSV file\nfile_path = '/kaggle/working/data_intervals_not_normalized1.csv'\n\n# Read the CSV file into a DataFrame\ndata_intervals_not_normalized = pd.read_csv(file_path)\n\n# # Display the first few rows to verify\n# print(data_intervals_not_normalized1.head())\n","metadata":{"execution":{"iopub.status.busy":"2024-11-14T11:24:41.297744Z","iopub.execute_input":"2024-11-14T11:24:41.298500Z","iopub.status.idle":"2024-11-14T11:24:44.607480Z","shell.execute_reply.started":"2024-11-14T11:24:41.298454Z","shell.execute_reply":"2024-11-14T11:24:44.606298Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Display the first few rows to verify\nprint(data_intervals_not_normalized.head())","metadata":{"execution":{"iopub.status.busy":"2024-11-14T14:27:19.780045Z","iopub.execute_input":"2024-11-14T14:27:19.780905Z","iopub.status.idle":"2024-11-14T14:27:19.835407Z","shell.execute_reply.started":"2024-11-14T14:27:19.780864Z","shell.execute_reply":"2024-11-14T14:27:19.834306Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"data_intervals_not_normalized.shape","metadata":{"execution":{"iopub.status.busy":"2024-11-14T11:25:13.206256Z","iopub.execute_input":"2024-11-14T11:25:13.206688Z","iopub.status.idle":"2024-11-14T11:25:13.214738Z","shell.execute_reply.started":"2024-11-14T11:25:13.206644Z","shell.execute_reply":"2024-11-14T11:25:13.213656Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Normalized PPG and GSR data for each sequence & using numerical values","metadata":{}},{"cell_type":"code","source":"# import os\n# import pandas as pd\n# from sklearn.preprocessing import StandardScaler\n\n# base_dir = '/kaggle/input/raw_data_ppg_gsr/'\n# data_intervals = []\n\n# # Function to load and process PPG or GSR data\n# def load_sensor_data(file_path, start_trigger, stop_trigger, value_column):\n#     data = pd.read_csv(file_path, names=['timestamp', value_column, 'start_stop_trigger'], na_values=[''])\n#     start_index = data[data['start_stop_trigger'] == start_trigger].index\n#     stop_index = data[data['start_stop_trigger'] == stop_trigger].index\n#     if not start_index.empty and not stop_index.empty:\n#         return data.loc[start_index[0]:stop_index[0], value_column].values\n#     else:\n#         return None\n\n# # Initialize scalers for PPG and GSR data\n# scaler_ppg = StandardScaler()\n# scaler_gsr = StandardScaler()\n\n# # Iterate over each subject's folder\n# for subject_id in os.listdir(base_dir):\n#     subject_path = os.path.join(base_dir, subject_id, subject_id)\n    \n#     # Check if the folder name is purely numeric (indicating a subject)\n#     if os.path.isdir(subject_path) and subject_id.isdigit():\n        \n#         # Load arousal and valence labels for this subject\n#         arousal_valence_path = os.path.join(subject_path, 'Arousal_Valence.csv')\n#         arousal_valence_df = pd.read_csv(arousal_valence_path, names=['video_id', 'valence', 'arousal', 'dominance'])\n        \n#         # Paths to GSR and PPG data files\n#         gsr_path = os.path.join(subject_path, 'raw_gsr.csv')\n#         ppg_path = os.path.join(subject_path, 'raw_ppg.csv')\n\n#         # Only process if both GSR and PPG data files exist\n#         if os.path.exists(gsr_path) and os.path.exists(ppg_path):\n\n#             for k in range(32):  # Loop over each video ID's start/stop triggers\n#                 start_trigger = k + 10\n#                 stop_trigger = k + 100\n\n#                 # Load GSR data interval\n#                 gsr_interval = load_sensor_data(gsr_path, start_trigger, stop_trigger, 'gsr_value')\n\n#                 # Load PPG data interval\n#                 ppg_interval = load_sensor_data(ppg_path, start_trigger, stop_trigger, 'ppg_value')\n\n#                 # Check if intervals are valid and retrieve labels\n#                 if gsr_interval is not None and ppg_interval is not None:\n#                     valence = arousal_valence_df.loc[arousal_valence_df['video_id'] == k, 'valence'].values[0]\n#                     arousal = arousal_valence_df.loc[arousal_valence_df['video_id'] == k, 'arousal'].values[0]\n\n#                     # Normalize the PPG and GSR data using the scalers\n#                     normalized_ppg = scaler_ppg.fit_transform(ppg_interval.reshape(-1, 1)).flatten()\n#                     normalized_gsr = scaler_gsr.fit_transform(gsr_interval.reshape(-1, 1)).flatten()\n\n#                     # Append data to the intervals list for model input\n#                     data_intervals.append({\n#                         'subject_id': subject_id,\n#                         'video_id': k,\n#                         'gsr_data': normalized_gsr,\n#                         'ppg_data': normalized_ppg,\n#                         'valence': valence,\n#                         'arousal': arousal\n#                     })\n\n# # Print a few samples to verify data\n# for data in data_intervals[:5]:  # Print first 5 samples as a check\n#     print(f\"Subject ID: {data['subject_id']}, Video ID: {data['video_id']}\")\n#     print(\"Valence:\", data['valence'])\n#     print(\"Arousal:\", data['arousal'])\n#     print(\"Normalized GSR Data:\", data['gsr_data'])\n#     print(\"Normalized PPG Data:\", data['ppg_data'])\n#     print(\"\\n\" + \"=\"*50 + \"\\n\")  # Separator between samples\n\n# # The data_intervals list now contains input sequences and labels for multi-input LSTM\n\n# print(data)\n","metadata":{"execution":{"iopub.status.busy":"2024-11-13T18:38:49.478198Z","iopub.execute_input":"2024-11-13T18:38:49.478903Z","iopub.status.idle":"2024-11-13T18:42:56.372784Z","shell.execute_reply.started":"2024-11-13T18:38:49.478864Z","shell.execute_reply":"2024-11-13T18:42:56.371756Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# print(len(data_intervals))","metadata":{"execution":{"iopub.status.busy":"2024-11-13T18:44:55.123356Z","iopub.execute_input":"2024-11-13T18:44:55.124457Z","iopub.status.idle":"2024-11-13T18:44:55.130246Z","shell.execute_reply.started":"2024-11-13T18:44:55.124399Z","shell.execute_reply":"2024-11-13T18:44:55.129006Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Save data_intervals to text file to load for future use","metadata":{}},{"cell_type":"code","source":"# import json\n# import os\n\n# # Define a path to save the files in the working directory\n# output_dir = '/kaggle/working/'\n\n# # Ensure the output directory exists\n# os.makedirs(output_dir, exist_ok=True)\n\n# # # Save the data_intervals to a JSON file\n# # def save_to_json(data_intervals, filename='data_intervals.json'):\n# #     file_path = os.path.join(output_dir, filename)\n# #     with open(file_path, 'w') as f:\n# #         json.dump(data_intervals, f)\n# #     print(f\"Data saved to {file_path}\")\n\n# # Save the data_intervals to a TXT file (saving as a readable string format)\n# def save_to_txt(data_intervals, filename='data_intervals.txt'):\n#     file_path = os.path.join(output_dir, filename)\n#     with open(file_path, 'w') as f:\n#         for item in data_intervals:\n#             f.write(str(item) + \"\\n\\n\")  # Each dictionary entry on a new line with a space between them\n#     print(f\"Data saved to {file_path}\")\n\n# # Save the data_intervals to both formats\n# # save_to_json(data_intervals)\n# save_to_txt(data_intervals)\n\n# # print(\"Data saved to both .json and .txt files.\")\n# print(\"Data saved to .txt file in Kaggle working directory.\")\n\n","metadata":{"execution":{"iopub.status.busy":"2024-11-13T18:48:40.68952Z","iopub.execute_input":"2024-11-13T18:48:40.689951Z","iopub.status.idle":"2024-11-13T18:48:43.701591Z","shell.execute_reply.started":"2024-11-13T18:48:40.689912Z","shell.execute_reply":"2024-11-13T18:48:43.700577Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Normalized PPG & GSR data with ","metadata":{}},{"cell_type":"code","source":"# import os\n# import pandas as pd\n# import json\n# from sklearn.preprocessing import StandardScaler\n\n# base_dir = '/kaggle/input/raw_data_ppg_gsr/'\n# data_intervals = []\n\n# # Function to load and process PPG or GSR data\n# def load_sensor_data(file_path, start_trigger, stop_trigger, value_column):\n#     data = pd.read_csv(file_path, names=['timestamp', value_column, 'start_stop_trigger'], na_values=[''])\n#     start_index = data[data['start_stop_trigger'] == start_trigger].index\n#     stop_index = data[data['start_stop_trigger'] == stop_trigger].index\n#     if not start_index.empty and not stop_index.empty:\n#         return data.loc[start_index[0]:stop_index[0], value_column].values\n#     else:\n#         return None\n\n# # Initialize scalers for PPG and GSR data\n# scaler_ppg = StandardScaler()\n# scaler_gsr = StandardScaler()\n\n# # Mapping function for arousal and valence levels\n# def map_level(value):\n#     if 1 <= value <= 3:\n#         return 'L'  # Low\n#     elif 4 <= value <= 6:\n#         return 'M'  # Medium\n#     elif 7 <= value <= 9:\n#         return 'H'  # High\n\n# # Iterate over each subject's folder\n# for subject_id in os.listdir(base_dir):\n#     subject_path = os.path.join(base_dir, subject_id, subject_id)\n    \n#     # Check if the folder name is purely numeric (indicating a subject)\n#     if os.path.isdir(subject_path) and subject_id.isdigit():\n        \n#         # Load arousal and valence labels for this subject\n#         arousal_valence_path = os.path.join(subject_path, 'Arousal_Valence.csv')\n#         arousal_valence_df = pd.read_csv(arousal_valence_path, names=['video_id', 'valence', 'arousal', 'dominance'])\n        \n#         # Paths to GSR and PPG data files\n#         gsr_path = os.path.join(subject_path, 'raw_gsr.csv')\n#         ppg_path = os.path.join(subject_path, 'raw_ppg.csv')\n\n#         # Only process if both GSR and PPG data files exist\n#         if os.path.exists(gsr_path) and os.path.exists(ppg_path):\n\n#             for k in range(32):  # Loop over each video ID's start/stop triggers\n#                 start_trigger = k + 10\n#                 stop_trigger = k + 100\n\n#                 # Load GSR data interval\n#                 gsr_interval = load_sensor_data(gsr_path, start_trigger, stop_trigger, 'gsr_value')\n\n#                 # Load PPG data interval\n#                 ppg_interval = load_sensor_data(ppg_path, start_trigger, stop_trigger, 'ppg_value')\n\n#                 # Check if intervals are valid and retrieve labels\n#                 if gsr_interval is not None and ppg_interval is not None:\n#                     valence = arousal_valence_df.loc[arousal_valence_df['video_id'] == k, 'valence'].values[0]\n#                     arousal = arousal_valence_df.loc[arousal_valence_df['video_id'] == k, 'arousal'].values[0]\n\n#                     # Map arousal and valence to Low, Medium, High\n#                     arousal_level = map_level(arousal)\n#                     valence_level = map_level(valence)\n#                     arousal_valence_label = f\"A{arousal_level}V{valence_level}\"\n\n#                     # Normalize the PPG and GSR data using the scalers\n#                     normalized_ppg = scaler_ppg.fit_transform(ppg_interval.reshape(-1, 1)).flatten()\n#                     normalized_gsr = scaler_gsr.fit_transform(gsr_interval.reshape(-1, 1)).flatten()\n\n#                     # Append data to the intervals list for model input\n#                     data_intervals.append({\n#                         'subject_id': subject_id,\n#                         'video_id': k,\n#                         'gsr_data': normalized_gsr.tolist(),\n#                         'ppg_data': normalized_ppg.tolist(),\n#                         'valence': valence,\n#                         'arousal': arousal,\n#                         'arousal_level': arousal_level,\n#                         'valence_level': valence_level,\n#                         'arousal_valence_label': arousal_valence_label\n#                     })\n\n# # # Save data_intervals to a .txt file\n# # output_path = '/kaggle/working/data_intervals_with_categories.txt'\n# # with open(output_path, 'w') as f:\n# #     json.dump(data_intervals, f)\n\n# # Function to load data_intervals from the .txt file\n# def load_data_intervals(file_path):\n#     with open(file_path, 'r') as f:\n#         loaded_data = json.load(f)\n#     return loaded_data\n\n# # Example usage of load_data_intervals\n# loaded_data_intervals = load_data_intervals(output_path)\n# # print(\"Loaded Data Intervals Sample:\", loaded_data_intervals[:1])  # Print first sample as a check\n","metadata":{"execution":{"iopub.status.busy":"2024-11-14T02:26:37.754586Z","iopub.execute_input":"2024-11-14T02:26:37.754878Z","iopub.status.idle":"2024-11-14T02:31:01.077348Z","shell.execute_reply.started":"2024-11-14T02:26:37.754845Z","shell.execute_reply":"2024-11-14T02:31:01.075927Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# No need to do this as preprocessed data is now stored in text file\n# Save to text file","metadata":{}},{"cell_type":"code","source":"# import os\n\n# # Define a path to save the files in the working directory\n# output_dir = '/kaggle/working/'\n\n# # Ensure the output directory exists\n# os.makedirs(output_dir, exist_ok=True)\n\n\n\n# # Save the data_intervals to a TXT file (saving as a readable string format)\n# def save_to_txt(data_intervals, filename='data_intervals_with_categories.txt'):\n#     file_path = os.path.join(output_dir, filename)\n#     with open(file_path, 'w') as f:\n#         for item in data_intervals:\n#             f.write(str(item) + \"\\n\\n\")  # Each dictionary entry on a new line with a space between them\n#     print(f\"Data saved to {file_path}\")\n\n\n# save_to_txt(data_intervals)\n\n# # print(\"Data saved to both .json and .txt files.\")\n# print(\"Data saved to .txt file in Kaggle working directory.\")","metadata":{"execution":{"iopub.status.busy":"2024-11-14T02:36:04.740762Z","iopub.execute_input":"2024-11-14T02:36:04.741209Z","iopub.status.idle":"2024-11-14T02:36:10.210763Z","shell.execute_reply.started":"2024-11-14T02:36:04.741171Z","shell.execute_reply":"2024-11-14T02:36:10.209766Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# import os\n\n# # Define a path to save the files in the working directory\n# output_dir = '/kaggle/working/'\n\n# # Ensure the output directory exists\n# os.makedirs(output_dir, exist_ok=True)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# print(len(data_intervals))","metadata":{"execution":{"iopub.status.busy":"2024-11-14T02:36:25.94938Z","iopub.execute_input":"2024-11-14T02:36:25.949776Z","iopub.status.idle":"2024-11-14T02:36:25.95495Z","shell.execute_reply.started":"2024-11-14T02:36:25.949739Z","shell.execute_reply":"2024-11-14T02:36:25.95381Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# If loading from input directory [ will work only after uploading the text file as input ]","metadata":{}},{"cell_type":"code","source":"# # # Load data from JSON file\n# # def load_from_json(filename='data_intervals.json'):\n# #     with open(filename, 'r') as f:\n# #         return json.load(f)\n\n# # Load data from TXT file\n# def load_from_txt(filename='/kaggle/input/multimodal-dataset-data-intervals-categories/data_intervals_with_categories.txt'):\n#     with open(filename, 'r') as f:\n#         data = f.readlines()\n#     return [eval(item.strip()) for item in data if item.strip()]  # Convert string representations back to dictionaries\n\n# # # Example of loading the saved data\n# # data_from_json = load_from_json()  # Load data from JSON file\n# data_from_txt = load_from_txt()    # Load data from TXT file\n\n# # print(\"Data loaded from JSON:\", data_from_json[:1])  # Print first item from loaded JSON data\n# print(\"Data loaded from TXT:\", data_from_txt[:1])    # Print first item from loaded TXT data\n\n# data_intervals = data_from_txt\n\n# print(len(data_intervals))\n\n","metadata":{"execution":{"iopub.status.busy":"2024-11-14T06:44:23.942172Z","iopub.execute_input":"2024-11-14T06:44:23.942459Z","iopub.status.idle":"2024-11-14T06:44:46.478714Z","shell.execute_reply.started":"2024-11-14T06:44:23.942426Z","shell.execute_reply":"2024-11-14T06:44:46.477767Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Convert to dataframe and csv file","metadata":{}},{"cell_type":"code","source":"# import pandas as pd\n\n# # Convert the data_intervals list of dictionaries to a DataFrame\n# data_intervals_dataframe = pd.DataFrame(data_intervals)\n\n# # Save the DataFrame to a CSV file\n# data_intervals_dataframe.to_csv('data_intervals.csv', index=False)\n\n# print(\"Data saved to data_intervals.csv\")\n","metadata":{"execution":{"iopub.status.busy":"2024-11-14T06:54:16.612626Z","iopub.execute_input":"2024-11-14T06:54:16.613001Z","iopub.status.idle":"2024-11-14T06:54:27.588920Z","shell.execute_reply.started":"2024-11-14T06:54:16.612965Z","shell.execute_reply":"2024-11-14T06:54:27.587995Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# LOAD THE CSV FILE HERE","metadata":{}},{"cell_type":"code","source":"import pandas as pd\n\n# Specify the path to the CSV file\nfile_path = '/kaggle/input/data-intervals-not-normalized-with-labels/data_intervals_not_normalized1.csv'\n\n# Read the CSV file into a DataFrame\ndata_intervals_dataframe = pd.read_csv(file_path)\n\n# Display the first few rows to verify\nprint(data_intervals_dataframe.head())\n\n","metadata":{"execution":{"iopub.status.busy":"2024-11-14T14:41:50.302162Z","iopub.execute_input":"2024-11-14T14:41:50.302538Z","iopub.status.idle":"2024-11-14T14:41:50.843120Z","shell.execute_reply.started":"2024-11-14T14:41:50.302500Z","shell.execute_reply":"2024-11-14T14:41:50.842031Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(data_intervals_dataframe.arousal_valence_label.nunique())\nprint(data_intervals_dataframe.valence_level.nunique())\nprint(data_intervals_dataframe.arousal_level.nunique())\n\nprint(data_intervals_dataframe.valence_level.nunique())\nprint(data_intervals_dataframe.arousal_level.nunique())\n\nprint(data_intervals_dataframe.valence.nunique())\nprint(data_intervals_dataframe.arousal.nunique())\n\nprint(data_intervals_dataframe.shape)\n","metadata":{"execution":{"iopub.status.busy":"2024-11-14T14:14:38.566063Z","iopub.execute_input":"2024-11-14T14:14:38.566422Z","iopub.status.idle":"2024-11-14T14:14:38.581700Z","shell.execute_reply.started":"2024-11-14T14:14:38.566383Z","shell.execute_reply":"2024-11-14T14:14:38.580616Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(\"subject id:\")\nprint(data_intervals_dataframe.subject_id.unique())\nprint(\"=================\")\nprint(\"Video id:\")\nprint(data_intervals_dataframe.video_id.unique())","metadata":{"execution":{"iopub.status.busy":"2024-11-14T14:14:42.623978Z","iopub.execute_input":"2024-11-14T14:14:42.624756Z","iopub.status.idle":"2024-11-14T14:14:42.631549Z","shell.execute_reply.started":"2024-11-14T14:14:42.624712Z","shell.execute_reply":"2024-11-14T14:14:42.630498Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# For PPG data extract features based on these steps :\n\n1. Filter data with savgol filter","metadata":{}},{"cell_type":"code","source":"from scipy.signal import savgol_filter\nimport pandas as pd\n\n# Assume data_intervals_dataframe is already loaded from the CSV\n# Convert ppg_data column from string representations of lists to actual lists\n# data_intervals_dataframe['ppg_data'] = data_intervals_dataframe['ppg_data'].apply(eval)\n# Check if the value is a string and apply eval only in that case\ndata_intervals_dataframe['ppg_data'] = data_intervals_dataframe['ppg_data'].apply(\n    lambda x: eval(x) if isinstance(x, str) else x\n)\n\n# Apply Savitzky-Golay filter to each entry in the 'ppg_data' column\npreprocessed_dataframe = data_intervals_dataframe.copy()\npreprocessed_dataframe['ppg_filtered_data'] = preprocessed_dataframe['ppg_data'].apply(\n    lambda x: savgol_filter(x, window_length=5, polyorder=3) if len(x) >= 5 else x\n)\n\n# Drop the original 'ppg_data' column if you no longer need it\npreprocessed_dataframe = preprocessed_dataframe.drop(columns=['ppg_data'])\n\n# Save the new dataframe to a CSV if needed\npreprocessed_dataframe.to_csv('/kaggle/working/preprocessed_data_intervals.csv', index=False)\n","metadata":{"execution":{"iopub.status.busy":"2024-11-14T14:41:59.272003Z","iopub.execute_input":"2024-11-14T14:41:59.272376Z","iopub.status.idle":"2024-11-14T14:42:15.560571Z","shell.execute_reply.started":"2024-11-14T14:41:59.272339Z","shell.execute_reply":"2024-11-14T14:42:15.559739Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(preprocessed_dataframe)","metadata":{"execution":{"iopub.status.busy":"2024-11-14T14:42:43.552307Z","iopub.execute_input":"2024-11-14T14:42:43.552814Z","iopub.status.idle":"2024-11-14T14:42:43.570229Z","shell.execute_reply.started":"2024-11-14T14:42:43.552772Z","shell.execute_reply":"2024-11-14T14:42:43.569144Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Increase window length to see a noticeable smoothing effect\nfrom scipy.signal import savgol_filter\nimport matplotlib.pyplot as plt\n\n# # Example of updating the preprocessed_dataframe with more noticeable filter\n# preprocessed_dataframe['ppg_filtered_data'] = data_intervals_dataframe['ppg_data'].apply(\n#     lambda x: savgol_filter(x, window_length=11, polyorder=3) if len(x) >= 11 else x\n# )\nsample_index = 53\n# Print the first few values for debugging\nprint(\"Original PPG data:\", data_intervals_dataframe.loc[sample_index, 'ppg_data'][:10])\nprint(\"Filtered PPG data:\", preprocessed_dataframe.loc[sample_index, 'ppg_filtered_data'][:10])\n\n# Now, re-plot\nsample_index = 53\noriginal_ppg_data = data_intervals_dataframe.loc[sample_index, 'ppg_data']\nfiltered_ppg_data = preprocessed_dataframe.loc[sample_index, 'ppg_filtered_data']\n\nplt.figure(figsize=(12, 6))\nplt.plot(original_ppg_data, label='Original PPG Data', color='blue')\nplt.plot(filtered_ppg_data, label='Filtered PPG Data (Savitzky-Golay)', color='red', linestyle='--')\nplt.xlabel('Time')\nplt.ylabel('PPG Signal')\nplt.legend()\nplt.title('Comparison of Original and Filtered PPG Data')\nplt.show()\n","metadata":{"execution":{"iopub.status.busy":"2024-11-14T14:41:44.764533Z","iopub.execute_input":"2024-11-14T14:41:44.765441Z","iopub.status.idle":"2024-11-14T14:41:44.802969Z","shell.execute_reply.started":"2024-11-14T14:41:44.765396Z","shell.execute_reply":"2024-11-14T14:41:44.801827Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Calculate Features\n\n\nCalculate Following features:\n1. mean\n2. median\n3. maximum\n4. variance\n5. standard deviation\n6. maximum\n7. minimum\n\nTime Domain Features:\n\n1. ranges\n2. rmssd: root mean square of successive differences\n3. sdsd : standard deviation of successive differences\n4. nni_50: number of normal-to-normal intervals greater than 50ms\n5. pnni_50: proportion of normal-to-normal intervals > 50ms\n6. nni_20: number of normal-to-normal intervals greater than 20ms. Count of successive heartbeats where time difference exceeds 20ms\n7. pnni_20: proportion of normal-to-normal intervals greater than 20ms\n8. avg_hr\n9. std_hr\n10. min_hr\n11. max_hr\n12. energy\n13. abs_sum_diff","metadata":{}},{"cell_type":"code","source":"def ranges(x):\n    return x.max() - x.min()\n\ndef rmssd(x):\n    return np.sqrt(np.mean(np.diff(x)**2))\n\ndef sdsd(x):\n    return st.stdev(np.diff(x))\n\ndef nni_50(x):\n    return sum(np.abs(np.diff(x)) > 50)\n\ndef pnni_50(x):\n    return 100* nni_50(x) / len(x)\n\ndef nni_20(x):\n    return sum(np.abs(np.diff(x)) >20)\n\ndef pnni_20(x):\n    return 100 * nni_20(x) / len(x)\n\ndef avg_hr(x):\n    return st.mean(60000/x)\n\ndef std_hr(x):\n    return st.stdev(60000/x)\n\ndef min_hr(x):\n    return min(60000/x)\n\ndef max_hr(x):\n    return max(60000/x)\n\ndef energy(x):\n    return sum(np.square(x))\n\ndef abs_sum_diff(x):\n    # sum of absolute differences (SAD) is a measure of the similarity between signal\n    return sum(np.abs(np.diff(x)))","metadata":{"execution":{"iopub.status.busy":"2024-11-14T11:38:05.373464Z","iopub.execute_input":"2024-11-14T11:38:05.373839Z","iopub.status.idle":"2024-11-14T11:38:05.384072Z","shell.execute_reply.started":"2024-11-14T11:38:05.373805Z","shell.execute_reply":"2024-11-14T11:38:05.383216Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np\nimport statistics as st\n\n# Define the custom functions if not already defined\ndef ranges(x):\n    return x.max() - x.min()\n\ndef rmssd(x):\n    return np.sqrt(np.mean(np.diff(x)**2))\n\ndef sdsd(x):\n    return st.stdev(np.diff(x))\n\ndef nni_50(x):\n    return sum(np.abs(np.diff(x)) > 50)\n\ndef pnni_50(x):\n    return 100 * nni_50(x) / len(x)\n\ndef nni_20(x):\n    return sum(np.abs(np.diff(x)) > 20)\n\ndef pnni_20(x):\n    return 100 * nni_20(x) / len(x)\n\ndef avg_hr(x):\n    # Avoid division by zero or NaN values\n    hr = 60000 / np.array(x)\n    hr = hr[np.isfinite(hr)]  # Remove non-finite values (e.g., inf, NaN)\n    return st.mean(hr) if len(hr) > 0 else 0  # Return 0 if no valid HR data\n\ndef std_hr(x):\n    # Avoid division by zero or NaN values\n    hr = 60000 / np.array(x)\n    hr = hr[np.isfinite(hr)]  # Remove non-finite values (e.g., inf, NaN)\n    return st.stdev(hr) if len(hr) > 1 else 0  # Return 0 if not enough valid data for std dev\n\ndef min_hr(x):\n    # Avoid division by zero or NaN values\n    hr = 60000 / np.array(x)\n    hr = hr[np.isfinite(hr)]  # Remove non-finite values (e.g., inf, NaN)\n    return min(hr) if len(hr) > 0 else 0  # Return 0 if no valid HR data\n\ndef max_hr(x):\n    # Avoid division by zero or NaN values\n    hr = 60000 / np.array(x)\n    hr = hr[np.isfinite(hr)]  # Remove non-finite values (e.g., inf, NaN)\n    return max(hr) if len(hr) > 0 else 0  # Return 0 if no valid HR data\n\ndef energy(x):\n    return sum(np.square(x))\n\ndef abs_sum_diff(x):\n    return sum(np.abs(np.diff(x)))\n\n# Apply the functions to 'ppg_filtered_data' in preprocessed_dataframe\ntime_features = preprocessed_dataframe['ppg_filtered_data'].apply(\n    lambda x: pd.Series({\n        'mean': np.mean(x),\n        'var': np.var(x),\n        'median': np.median(x),\n        'max': np.max(x),\n        'min': np.min(x),\n        'range': ranges(x),\n        'rmssd': rmssd(x),\n        'sdsd': sdsd(x),\n        'nni_50': nni_50(x),\n        'pnni_50': pnni_50(x),\n        'nni_20': nni_20(x),\n        'pnni_20': pnni_20(x),\n        'avg_hr': avg_hr(x),\n        'std_hr': std_hr(x),\n        'min_hr': min_hr(x),\n        'max_hr': max_hr(x),\n        'energy': energy(x),\n        'abs_sum_diff': abs_sum_diff(x)\n    })\n)\n\n# Reset the index to align with the preprocessed_dataframe\ntime_features = time_features.reset_index(drop=True)\n\n# Add the 'subject_id', 'video_id', and 'arousal_valence_label' to the time_features dataframe\ntime_features['subject_id'] = preprocessed_dataframe['subject_id']\ntime_features['video_id'] = preprocessed_dataframe['video_id']\ntime_features['arousal_valence_label'] = preprocessed_dataframe['arousal_valence_label']\n\n# Optionally, check the result\nprint(time_features.head())\n","metadata":{"execution":{"iopub.status.busy":"2024-11-14T14:44:09.862469Z","iopub.execute_input":"2024-11-14T14:44:09.863348Z","iopub.status.idle":"2024-11-14T14:45:00.011315Z","shell.execute_reply.started":"2024-11-14T14:44:09.863307Z","shell.execute_reply":"2024-11-14T14:45:00.010295Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# PPG DATA EXTRACTED FEATURES FOR TIME DOMAIN","metadata":{}},{"cell_type":"code","source":"print(time_features.shape) ","metadata":{"execution":{"iopub.status.busy":"2024-11-14T14:46:00.113729Z","iopub.execute_input":"2024-11-14T14:46:00.114159Z","iopub.status.idle":"2024-11-14T14:46:00.119153Z","shell.execute_reply.started":"2024-11-14T14:46:00.114120Z","shell.execute_reply":"2024-11-14T14:46:00.118219Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Save time features to csv","metadata":{}},{"cell_type":"code","source":"# Save the DataFrame to a CSV file\ntime_features.to_csv('ppg_time_features.csv', index=False)\n\nprint(\"Data saved to ppg_time_features.csv\")\n\n# Specify the path to the CSV file\nfile_path = '/kaggle/working/ppg_time_features.csv'\n\n# Read the CSV file into a DataFrame\ntime_features = pd.read_csv(file_path)","metadata":{"execution":{"iopub.status.busy":"2024-11-14T14:47:25.475138Z","iopub.execute_input":"2024-11-14T14:47:25.475488Z","iopub.status.idle":"2024-11-14T14:47:25.574922Z","shell.execute_reply.started":"2024-11-14T14:47:25.475455Z","shell.execute_reply":"2024-11-14T14:47:25.573994Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(time_features)","metadata":{"execution":{"iopub.status.busy":"2024-11-14T14:47:49.446830Z","iopub.execute_input":"2024-11-14T14:47:49.447720Z","iopub.status.idle":"2024-11-14T14:47:49.466346Z","shell.execute_reply.started":"2024-11-14T14:47:49.447674Z","shell.execute_reply":"2024-11-14T14:47:49.465190Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Frequency Features","metadata":{}},{"cell_type":"code","source":"from scipy import signal \nfrom scipy.ndimage import label\nfrom scipy.stats import zscore\nfrom scipy.interpolate import interp1d\nfrom scipy import integrate\nfrom scipy.integrate import *  # this will include trapz. BUT using a wildcard import is not recommended as \n# it imports all functions and variables from the scipy.integrate module which can cause namespace pollution, conflicts btw similarly\n# named functions in different libraries\n\n# from scipy.integrate import trapz\n# from scipy import trapz\n\nfrom numpy import trapz # ORIGINALLY USED FROM SCIPY.INTEGRATE IMPORT TRAPZ WHICH DID NOT WORK","metadata":{"execution":{"iopub.status.busy":"2024-11-14T11:49:31.069380Z","iopub.execute_input":"2024-11-14T11:49:31.070174Z","iopub.status.idle":"2024-11-14T11:49:32.056135Z","shell.execute_reply.started":"2024-11-14T11:49:31.070126Z","shell.execute_reply":"2024-11-14T11:49:32.055031Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(preprocessed_dataframe)","metadata":{"execution":{"iopub.status.busy":"2024-11-14T14:10:58.804808Z","iopub.execute_input":"2024-11-14T14:10:58.805759Z","iopub.status.idle":"2024-11-14T14:10:59.131540Z","shell.execute_reply.started":"2024-11-14T14:10:58.805713Z","shell.execute_reply":"2024-11-14T14:10:59.130197Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# import numpy as np\n# import pandas as pd\n# import ast\n# import re\n\n# # Example of how to preprocess the 'ppg_filtered_data' column\n# def preprocess_ppg_data(df):\n#     for i in range(len(df)):\n#         try:\n#             ppg_str = df['ppg_filtered_data'].iloc[i]\n#             # Remove any extraneous spaces, line breaks, and ellipses\n#             ppg_str = re.sub(r'\\s+', ',', ppg_str.replace(\"...\", \"\"))\n#             # Use ast.literal_eval to safely evaluate as a list\n#             ppg_list = np.array(ast.literal_eval(ppg_str), dtype=float)\n#             # Replace the string with the actual list of floats\n#             df.at[i, 'ppg_filtered_data'] = ppg_list\n#         except (ValueError, SyntaxError, TypeError) as e:\n#             print(f\"Error converting PPG data at index {i}: {e}\")\n#             continue\n\n# # Call the function on the dataframe\n# preprocess_ppg_data(preprocessed_dataframe)\n\n# Confirm the conversion worked by printing the first row\nprint(preprocessed_dataframe['ppg_filtered_data'].iloc[0])\n","metadata":{"execution":{"iopub.status.busy":"2024-11-14T12:14:31.966664Z","iopub.execute_input":"2024-11-14T12:14:31.967064Z","iopub.status.idle":"2024-11-14T12:14:31.974702Z","shell.execute_reply.started":"2024-11-14T12:14:31.967026Z","shell.execute_reply":"2024-11-14T12:14:31.973569Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"preprocessed_dataframe.shape","metadata":{"execution":{"iopub.status.busy":"2024-11-14T12:14:55.443499Z","iopub.execute_input":"2024-11-14T12:14:55.443900Z","iopub.status.idle":"2024-11-14T12:14:55.451701Z","shell.execute_reply.started":"2024-11-14T12:14:55.443863Z","shell.execute_reply":"2024-11-14T12:14:55.450591Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# from scipy.interpolate import interp1d\n# import numpy as np\n# import pandas as pd\n# from scipy.interpolate import interp1d\n# from scipy import signal\n# from numpy import trapz # ORIGINALLY USED FROM SCIPY.INTEGRATE IMPORT TRAPZ WHICH DID NOT WORK\n# import matplotlib.pyplot as plt\n\n# # Assuming preprocessed_dataframe is already loaded\n\n# # Interpolate the PPG data\n# ppg_interpolated = []\n# for i in range(len(preprocessed_dataframe)):\n#     ppg_signal = preprocessed_dataframe['ppg_filtered_data'].iloc[i]  # Get the PPG signal for each subject/video\n#     x = np.cumsum(ppg_signal) / 1000.0  # Cumulative sum of PPG signal (time base)\n#     f = interp1d(x, ppg_signal, kind='cubic', fill_value=\"extrapolate\")  # Interpolation function\n#     fs = 4.0  # New sampling frequency\n#     steps = 1 / fs\n\n#     # Sample from the interpolation function at the new time points\n#     xx = np.arange(1, np.max(x), steps)\n#     ppg_interpolated.append(f(xx))\n\n# # Check dimensions of interpolated data\n# print(len(ppg_interpolated), ppg_interpolated[0].shape)\n\n# # Plot original and interpolated PPG signals for comparison\n# plt.plot(preprocessed_dataframe['ppg_filtered_data'].iloc[0], label='Original PPG Data')\n# plt.plot(ppg_interpolated[0], label='Interpolated PPG Data')\n# plt.xlabel('Time')\n# plt.ylabel('PPG Signal')\n# plt.title('Original and Interpolated PPG Data')\n# plt.legend()\n# plt.show()\n\n# # Frequency domain function to calculate frequency features\n# def frequency_domain(ppg_signal, fs=4):\n#     # Estimate the spectral density using Welch's method\n#     fxx, pxx = signal.welch(x=ppg_signal, fs=fs)\n\n#     # Segment frequencies into bands\n#     cond_vlf = (fxx >= 0) & (fxx < 0.04)\n#     cond_lf = (fxx >= 0.04) & (fxx < 0.15)\n#     cond_hf = (fxx >= 0.15) & (fxx < 0.4)\n\n#     # Calculate power in each band by integrating the spectral density\n#     vlf = trapz(pxx[cond_vlf], fxx[cond_vlf])\n#     lf = trapz(pxx[cond_lf], fxx[cond_lf])\n#     hf = trapz(pxx[cond_hf], fxx[cond_hf])\n\n#     # Sum these up to get total power\n#     total_power = vlf + lf + hf\n\n#     # Find which frequency has the most power in each band\n#     peak_vlf = fxx[cond_vlf][np.argmax(pxx[cond_vlf])]\n#     peak_lf = fxx[cond_lf][np.argmax(pxx[cond_lf])]\n#     peak_hf = fxx[cond_hf][np.argmax(pxx[cond_hf])]\n\n#     # Fraction of LF and HF\n#     lf_nu = 100 * lf / (lf + hf)\n#     hf_nu = 100 * hf / (lf + hf)\n\n#     # Return results\n#     result = [vlf, lf, hf, total_power, lf / hf, peak_vlf, peak_lf, peak_hf, lf_nu, hf_nu]\n#     return np.array(result), fxx, pxx\n\n# # Extract frequency features from the interpolated PPG data\n# freq_feat = []\n# for i in range(len(ppg_interpolated)):\n#     results, fxx, pxx = frequency_domain(ppg_interpolated[i])\n#     freq_feat.append(results)\n\n# # Convert list to numpy array and create a DataFrame with frequency features\n# freq_col = ['vlf', 'lf', 'hf', 'tot_pow', 'lf_hf_ratio', 'peak_vlf', 'peak_lf', 'peak_hf', 'lf_nu', 'hf_nu']\n# freq_features = pd.DataFrame(freq_feat, columns=freq_col)\n\n# # Add 'subject_id', 'video_id', and 'arousal_valence_label' to the frequency features DataFrame\n# freq_features['subject_id'] = preprocessed_dataframe['subject_id']\n# freq_features['video_id'] = preprocessed_dataframe['video_id']\n# freq_features['arousal_valence_label'] = preprocessed_dataframe['arousal_valence_label']\n\n# # Optionally, check the result\n# print(freq_features.head())\n\n","metadata":{"execution":{"iopub.status.busy":"2024-11-14T12:15:03.048334Z","iopub.execute_input":"2024-11-14T12:15:03.049123Z","iopub.status.idle":"2024-11-14T12:15:03.420705Z","shell.execute_reply.started":"2024-11-14T12:15:03.049081Z","shell.execute_reply":"2024-11-14T12:15:03.419390Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"data_intervals_dataframe.shape","metadata":{"execution":{"iopub.status.busy":"2024-11-14T14:33:25.915484Z","iopub.execute_input":"2024-11-14T14:33:25.915961Z","iopub.status.idle":"2024-11-14T14:33:25.925028Z","shell.execute_reply.started":"2024-11-14T14:33:25.915915Z","shell.execute_reply":"2024-11-14T14:33:25.923916Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Without Savgol filter","metadata":{}},{"cell_type":"code","source":"from scipy.interpolate import interp1d\nimport numpy as np\nimport pandas as pd\nfrom scipy.interpolate import interp1d\nfrom scipy import signal\nfrom scipy.signal import savgol_filter\nfrom numpy import trapz # ORIGINALLY USED FROM SCIPY.INTEGRATE IMPORT TRAPZ WHICH DID NOT WORK\nimport matplotlib.pyplot as plt\n\n\n\n\n# Function to apply Savitzky-Golay filter on ppg_data column\ndef preprocess_ppg_data_with_savgol(df):\n    # Convert 'ppg_data' to list of floats if in string format\n    df['ppg_data'] = df['ppg_data'].apply(lambda x: np.array(list(map(float, x.split(',')))) if isinstance(x, str) else x)\n\n    # Apply Savitzky-Golay filter and store in 'ppg_filtered_data'\n    df['ppg_filtered_data'] = df['ppg_data'].apply(lambda ppg: savgol_filter(ppg, window_length=5, polyorder=3))\n\n    return df\n\n# Apply preprocessing function to create 'ppg_filtered_data' column\npreprocessed_dataframe_new = preprocess_ppg_data_with_savgol(data_intervals_dataframe)\n\n# Verify the result\nprint(preprocessed_dataframe_new[['ppg_data', 'ppg_filtered_data']].head())\n# Apply preprocessing function to create 'ppg_filtered_data' column\npreprocessed_dataframe_new = preprocess_ppg_data_with_savgol(data_intervals_dataframe)\n\n# Step 2: Interpolate and extract frequency features from 'ppg_filtered_data'\n# Interpolation function for PPG signal\ndef interpolate_ppg(ppg_signal):\n    # Define a time array based on a constant sampling interval (100 Hz)\n    x = np.linspace(0, len(ppg_signal) / 100.0, num=len(ppg_signal))  # 100 Hz sampling\n    f = interp1d(x, ppg_signal, kind='cubic', fill_value=\"extrapolate\")\n    \n    # Define new time points for the interpolated signal (4 Hz sampling)\n    fs = 4.0  # New sampling frequency\n    new_x = np.arange(0, x[-1], 1 / fs)\n    \n    # Interpolate the signal at the new time points\n    interpolated_signal = f(new_x)\n    return interpolated_signal\n\n# Frequency domain function to calculate frequency features\ndef frequency_domain(ppg_signal, fs=4):\n    fxx, pxx = signal.welch(x=ppg_signal, fs=fs)\n    cond_vlf = (fxx >= 0) & (fxx < 0.04)\n    cond_lf = (fxx >= 0.04) & (fxx < 0.15)\n    cond_hf = (fxx >= 0.15) & (fxx < 0.4)\n    vlf = trapz(pxx[cond_vlf], fxx[cond_vlf])\n    lf = trapz(pxx[cond_lf], fxx[cond_lf])\n    hf = trapz(pxx[cond_hf], fxx[cond_hf])\n    total_power = vlf + lf + hf\n    peak_vlf = fxx[cond_vlf][np.argmax(pxx[cond_vlf])] if np.any(cond_vlf) else 0\n    peak_lf = fxx[cond_lf][np.argmax(pxx[cond_lf])] if np.any(cond_lf) else 0\n    peak_hf = fxx[cond_hf][np.argmax(pxx[cond_hf])] if np.any(cond_hf) else 0\n    lf_nu = 100 * lf / (lf + hf) if (lf + hf) != 0 else 0\n    hf_nu = 100 * hf / (lf + hf) if (lf + hf) != 0 else 0\n    return [vlf, lf, hf, total_power, lf / hf if hf != 0 else 0, peak_vlf, peak_lf, peak_hf, lf_nu, hf_nu]\n\n# Step 3: Interpolate and extract frequency features for each PPG filtered signal\nppg_interpolated = preprocessed_dataframe_new['ppg_filtered_data'].apply(interpolate_ppg)\n# Verify the result\nprint(ppg_interpolated.head())\n\nfreq_feat = ppg_interpolated.apply(frequency_domain)\n\n# Convert frequency features to a DataFrame and add identifiers\nfreq_col = ['vlf', 'lf', 'hf', 'tot_pow', 'lf_hf_ratio', 'peak_vlf', 'peak_lf', 'peak_hf', 'lf_nu', 'hf_nu']\nfreq_features = pd.DataFrame(freq_feat.tolist(), columns=freq_col)\nfreq_features['subject_id'] = preprocessed_dataframe_new['subject_id']\nfreq_features['video_id'] = preprocessed_dataframe_new['video_id']\nfreq_features['arousal_valence_label'] = preprocessed_dataframe_new['arousal_valence_label']\n\n# Optional: Check the frequency features\nprint(freq_features.head())\n","metadata":{"execution":{"iopub.status.busy":"2024-11-14T14:39:11.299102Z","iopub.execute_input":"2024-11-14T14:39:11.299941Z","iopub.status.idle":"2024-11-14T14:39:15.988198Z","shell.execute_reply.started":"2024-11-14T14:39:11.299898Z","shell.execute_reply":"2024-11-14T14:39:15.987382Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Save frequency features to csv file","metadata":{}},{"cell_type":"code","source":"\n\n# Save the DataFrame to a CSV file\nfreq_features.to_csv('ppg_freq_features.csv', index=False)\n\nprint(\"Data saved to ppg_freq_features.csv\")\n\n# Specify the path to the CSV file\nfile_path = '/kaggle/working/ppg_freq_features.csv'\n\n# Read the CSV file into a DataFrame\nppg_freq_features = pd.read_csv(file_path)\n\n\nprint(ppg_freq_features.head())\n","metadata":{"execution":{"iopub.status.busy":"2024-11-14T14:52:30.277667Z","iopub.execute_input":"2024-11-14T14:52:30.278333Z","iopub.status.idle":"2024-11-14T14:52:30.344121Z","shell.execute_reply.started":"2024-11-14T14:52:30.278291Z","shell.execute_reply":"2024-11-14T14:52:30.343239Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ppg_freq_features.shape","metadata":{"execution":{"iopub.status.busy":"2024-11-14T14:52:46.903611Z","iopub.execute_input":"2024-11-14T14:52:46.904476Z","iopub.status.idle":"2024-11-14T14:52:46.910164Z","shell.execute_reply.started":"2024-11-14T14:52:46.904429Z","shell.execute_reply":"2024-11-14T14:52:46.909361Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(time_features)","metadata":{"execution":{"iopub.status.busy":"2024-11-14T14:49:03.944200Z","iopub.execute_input":"2024-11-14T14:49:03.945106Z","iopub.status.idle":"2024-11-14T14:49:03.962249Z","shell.execute_reply.started":"2024-11-14T14:49:03.945063Z","shell.execute_reply":"2024-11-14T14:49:03.961347Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ppg_freq_features.columns","metadata":{"execution":{"iopub.status.busy":"2024-11-14T15:03:41.959092Z","iopub.execute_input":"2024-11-14T15:03:41.959490Z","iopub.status.idle":"2024-11-14T15:03:41.966646Z","shell.execute_reply.started":"2024-11-14T15:03:41.959452Z","shell.execute_reply":"2024-11-14T15:03:41.965764Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"time_features.columns","metadata":{"execution":{"iopub.status.busy":"2024-11-14T15:04:58.256882Z","iopub.execute_input":"2024-11-14T15:04:58.257948Z","iopub.status.idle":"2024-11-14T15:04:58.265548Z","shell.execute_reply.started":"2024-11-14T15:04:58.257886Z","shell.execute_reply":"2024-11-14T15:04:58.264476Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Concatenate time and frequency features of PPG","metadata":{}},{"cell_type":"code","source":"# Drop common columns from ppg_freq_features dataframe\nppg_freq_features_unique = ppg_freq_features.drop(columns=['subject_id', 'video_id', 'arousal_valence_label'])\n\n# Concatenate the dataframes\nppg_features_extracted = pd.concat([time_features, ppg_freq_features_unique], axis=1)\n\n# Verify the result\nprint(ppg_features_extracted.columns)\n","metadata":{"execution":{"iopub.status.busy":"2024-11-14T15:07:58.819212Z","iopub.execute_input":"2024-11-14T15:07:58.820160Z","iopub.status.idle":"2024-11-14T15:07:58.828402Z","shell.execute_reply.started":"2024-11-14T15:07:58.820115Z","shell.execute_reply":"2024-11-14T15:07:58.827462Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# READ ALL PPG EXTRACTED FEATURES CSV HERE","metadata":{}},{"cell_type":"code","source":"\nprint(ppg_features_extracted.head())\n\nppg_features_extracted.shape\n\nprint(\"===============================================\")\n\nppg_features_extracted.to_csv(\"ppg_all_features.csv\", index=False)\n\n\nprint(\"Data saved to ppg_all_features.csv\")\n\n# Specify the path to the CSV file\nfile_path = '/kaggle/working/ppg_all_features.csv'\n\n# Read the CSV file into a DataFrame\nppg_all_features = pd.read_csv(file_path)\n\n\nprint(ppg_all_features.head())\n\n","metadata":{"execution":{"iopub.status.busy":"2024-11-14T15:12:36.862764Z","iopub.execute_input":"2024-11-14T15:12:36.863488Z","iopub.status.idle":"2024-11-14T15:12:37.029168Z","shell.execute_reply.started":"2024-11-14T15:12:36.863448Z","shell.execute_reply":"2024-11-14T15:12:37.028228Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"labels = ppg_all_features['arousal_valence_label']\nprint(labels)\n\nppg_only_features =ppg_all_features.drop(columns=['subject_id', 'video_id', 'arousal_valence_label'])\nprint(ppg_only_features.head())","metadata":{"execution":{"iopub.status.busy":"2024-11-14T15:29:24.757949Z","iopub.execute_input":"2024-11-14T15:29:24.758360Z","iopub.status.idle":"2024-11-14T15:29:24.778103Z","shell.execute_reply.started":"2024-11-14T15:29:24.758320Z","shell.execute_reply":"2024-11-14T15:29:24.776696Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# EXTRACT GSR FEATURES","metadata":{}},{"cell_type":"code","source":"import pandas as pd\n\n# Specify the path to the CSV file\nfile_path = '/kaggle/input/data-intervals-not-normalized-with-labels/data_intervals_not_normalized1.csv'\n\n# Read the CSV file into a DataFrame\ndata_intervals_dataframe = pd.read_csv(file_path)\n\n# Display the first few rows to verify\nprint(data_intervals_dataframe.head())","metadata":{"execution":{"iopub.status.busy":"2024-11-15T03:34:33.551566Z","iopub.execute_input":"2024-11-15T03:34:33.552006Z","iopub.status.idle":"2024-11-15T03:34:35.139214Z","shell.execute_reply.started":"2024-11-15T03:34:33.551962Z","shell.execute_reply":"2024-11-15T03:34:35.138135Z"},"trusted":true},"execution_count":2,"outputs":[{"name":"stdout","text":"   subject_id  video_id                                           gsr_data  \\\n0           7         0  179125.171875,178570.375,179391.03125,181556.2...   \n1           7         1  182172.671875,182666.59375,183080.09375,184354...   \n2           7         2  168857.21875,168781.234375,169132.53125,169823...   \n3           7         3  178895.96875,178088.4375,177970.546875,177919....   \n4           7         4  195028.421875,195278.421875,195498.46875,19572...   \n\n                                            ppg_data  valence  arousal  \\\n0  612293.0,611442.0,610770.0,610083.0,609360.0,6...        4        5   \n1  551232.0,551091.0,550905.0,550746.0,550549.0,5...        3        5   \n2  605678.0,605853.0,606119.0,606351.0,606579.0,6...        4        5   \n3  565274.0,565598.0,566007.0,566224.0,566272.0,5...        5        6   \n4  549417.0,549378.0,549404.0,549342.0,549404.0,5...        6        6   \n\n  arousal_level valence_level arousal_valence_label  \n0             M             M                  AMVM  \n1             M             L                  AMVL  \n2             M             M                  AMVM  \n3             M             M                  AMVM  \n4             M             M                  AMVM  \n","output_type":"stream"}]},{"cell_type":"markdown","source":"# Apply CWT to GSR","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"# Print the first value in the 'gsr_data' column (first row) and its type\nfirst_value = data_intervals_dataframe['gsr_data'].iloc[0]\nprint(\"First value in 'gsr_data':\", first_value)\nprint(\"Type of the first value:\", type(first_value))\n\n# Print the third value in the 'gsr_data' column (third row) and its type\nthird_value = data_intervals_dataframe['gsr_data'].iloc[2]\nprint(\"Third value in 'gsr_data':\", third_value)\nprint(\"Type of the third value:\", type(third_value))\n","metadata":{"execution":{"iopub.status.busy":"2024-11-15T02:05:53.821361Z","iopub.execute_input":"2024-11-15T02:05:53.821898Z","iopub.status.idle":"2024-11-15T02:05:53.830215Z","shell.execute_reply.started":"2024-11-15T02:05:53.821849Z","shell.execute_reply":"2024-11-15T02:05:53.828955Z"},"trusted":true},"execution_count":2,"outputs":[{"name":"stdout","text":"First value in 'gsr_data': 179125.171875,178570.375,179391.03125,181556.21875,182420.515625,183646.203125,183727.078125,183940.0,184140.109375,184302.125,184497.453125,184425.84375,184151.828125,183712.84375,183200.3125,182930.90625,182558.046875,182371.484375,182351.875,182348.90625,182571.71875,182762.59375,182850.78125,183005.96875,183131.21875,183223.3125,183341.90625,183501.390625,183623.203125,183718.921875,183845.875,183990.0625,184070.0625,184106.53125,184181.75,184225.921875,184179.484375,184101.109375,184012.8125,183851.015625,183785.625,183725.21875,183676.765625,183682.390625,183646.03125,183785.234375,183910.671875,183894.5,184009.25,184028.03125,184072.859375,184162.5625,184114.109375,184188.609375,184215.5,184219.015625,184314.234375,184334.859375,184143.5625,183615.875,182805.71875,181927.59375,180957.71875,180380.265625,180069.375,179868.71875,179733.6875,179904.6875,180134.609375,180490.90625,180799.6875,181055.21875,181257.625,181439.09375,181634.703125,181800.921875,181920.0625,182107.703125,182163.03125,182333.625,182389.96875,182461.3125,182565.28125,182543.171875,182640.984375,182654.921875,182691.265625,182827.484375,182866.15625,182997.203125,183045.8125,183130.515625,183130.046875\nType of the first value: <class 'str'>\nThird value in 'gsr_data': 168857.21875,168781.234375,169132.53125,169823.0,170710.34375,171388.953125,172187.515625,172716.078125,173357.40625,173904.28125,174323.5625,174718.265625,175014.71875,175309.296875,175530.109375,175777.78125,175984.109375,176195.375,176381.4375,176576.296875,176749.59375,176954.421875,177131.3125,177294.34375,177346.453125,177531.609375,177766.203125,177894.71875,177950.171875,178103.15625,178181.046875,178255.0,178363.609375,178503.625,178543.53125,178680.40625,178786.609375,178833.0625,178843.828125,179016.65625,179132.59375,179219.21875,179301.3125,179397.328125,179474.671875,179588.25,179678.40625,179676.828125,179090.765625,177644.015625,175528.703125,173140.734375,171016.78125,169359.78125,168094.109375,167512.359375,167284.734375,167543.796875,168265.234375,169093.046875,169971.828125,170824.0625,171611.28125,172348.578125,172893.34375,173391.296875,173790.359375,174225.703125,174506.8125,174826.515625,175100.28125,175360.75,175597.671875,175794.1875,176006.546875,176196.96875,176361.34375,176525.625,176728.859375,176877.265625,177051.09375,177108.890625,177245.3125,177345.75,177275.859375,176441.875,175111.859375,173636.34375,172269.125,171142.0,170401.078125,169992.875,169993.890625,170285.96875,170852.0,171463.015625,172214.46875,172766.828125,173270.65625,173752.75,173954.890625,173402.5625,171716.21875,169250.234375,166271.671875,163741.578125,161828.671875,160911.34375,160645.578125,160936.40625,161622.578125,162663.734375,163788.28125,164938.421875,165878.953125,166861.53125,167621.125,168391.609375,169140.03125,169697.65625,170152.859375,170501.0625,170859.03125,171183.90625,171558.15625,171780.59375,172048.140625,172277.703125,172467.484375\nType of the third value: <class 'str'>\n","output_type":"stream"}]},{"cell_type":"code","source":"# # Print the first value in the 'gsr_data' column (first row) and its type\n# first_value = data_intervals_dataframe['gsr_data'].iloc[0]\n# print(\"First value in 'gsr_data':\", first_value)\n# print(\"Type of the first value:\", type(first_value))\n\n# # Print the third value in the 'gsr_data' column (third row) and its type\n# third_value = data_intervals_dataframe['gsr_data'].iloc[2]\n# print(\"Third value in 'gsr_data':\", third_value)\n# print(\"Type of the third value:\", type(third_value))\n\n\n# Print the third value in the 'gsr_data' column (third row) and its type\ndataframe_columns = data_intervals_dataframe.columns\nprint(dataframe_columns)\n\nppg_third_value = data_intervals_dataframe['ppg_data'].iloc[2]\nprint(ppg_third_value)","metadata":{"execution":{"iopub.status.busy":"2024-11-15T02:08:32.333588Z","iopub.execute_input":"2024-11-15T02:08:32.335205Z","iopub.status.idle":"2024-11-15T02:08:32.345404Z","shell.execute_reply.started":"2024-11-15T02:08:32.335132Z","shell.execute_reply":"2024-11-15T02:08:32.343703Z"},"trusted":true},"execution_count":6,"outputs":[{"name":"stdout","text":"Index(['subject_id', 'video_id', 'gsr_data', 'ppg_data', 'valence', 'arousal',\n       'arousal_level', 'valence_level', 'arousal_valence_label'],\n      dtype='object')\n605678.0,605853.0,606119.0,606351.0,606579.0,606769.0,607007.0,607253.0,607461.0,607620.0,607842.0,608037.0,608306.0,608448.0,608667.0,608855.0,609069.0,609291.0,609425.0,609642.0,609932.0,610125.0,610256.0,610579.0,610784.0,611056.0,611287.0,611514.0,611654.0,611746.0,611636.0,611449.0,611136.0,610584.0,610026.0,609417.0,608837.0,608262.0,607743.0,607359.0,607022.0,606744.0,606540.0,606445.0,606329.0,606268.0,606358.0,606327.0,606375.0,606543.0,606663.0,606834.0,606958.0,607096.0,607212.0,607329.0,607455.0,607436.0,607453.0,607463.0,607457.0,607437.0,607499.0,607558.0,607631.0,607699.0,607762.0,607910.0,608074.0,608214.0,608341.0,608603.0,608765.0,608952.0,609168.0,609381.0,609607.0,609818.0,610038.0,610252.0,610372.0,610683.0,610914.0,611065.0,611281.0,611536.0,611779.0,611920.0,612183.0,612342.0,612593.0,612763.0,612978.0,613172.0,613388.0,613648.0,613747.0,613946.0,614198.0,614475.0,614708.0,614886.0,615178.0,615468.0,615711.0,615913.0,616041.0,616133.0,616019.0,615753.0,615466.0,614929.0,614316.0,613761.0,613129.0,612544.0,612025.0,611578.0,611249.0,610910.0,610662.0,610522.0,610372.0,610222.0,610175.0,610137.0,610105.0,610129.0,610244.0,610315.0,610416.0,610566.0,610600.0,610745.0,610873.0,610850.0,610896.0,610871.0,610893.0,610936.0,610927.0,610926.0,610949.0,610983.0,611118.0,611243.0,611407.0,611405.0,611604.0,611728.0,611926.0,612116.0,612219.0,612454.0,612651.0,612857.0,613012.0,613192.0,613319.0,613557.0,613736.0,613923.0,614125.0,614315.0,614503.0,614727.0,614905.0,615061.0,615266.0,615456.0,615654.0,615816.0,615935.0,616184.0,616389.0,616514.0,616704.0,616886.0,617140.0,617359.0,617552.0,617776.0,618015.0,618201.0,618433.0,618609.0,618839.0,619035.0,619254.0,619448.0,619730.0,619963.0,620163.0,620379.0,620623.0,620817.0,621010.0,621020.0,620935.0,620653.0,620218.0,619689.0,619063.0,618376.0,617645.0,616926.0,616308.0,615754.0,615323.0,614949.0,614529.0,614285.0,614029.0,613725.0,613539.0,613362.0,613153.0,613052.0,612983.0,612902.0,612858.0,612804.0,612874.0,612814.0,612808.0,612719.0,612673.0,612566.0,612468.0,612408.0,612352.0,612284.0,612223.0,612168.0,612153.0,612141.0,612181.0,612126.0,612292.0,612237.0,612346.0,612436.0,612460.0,612595.0,612718.0,612837.0,612987.0,613023.0,613155.0,613336.0,613451.0,613544.0,613708.0,613839.0,613924.0,613997.0,614152.0,614264.0,614356.0,614463.0,614640.0,614747.0,614902.0,615015.0,615123.0,615214.0,615432.0,615516.0,615662.0,615822.0,615919.0,616069.0,616234.0,616432.0,616541.0,616661.0,616827.0,617006.0,617084.0,617281.0,617474.0,617615.0,617779.0,617958.0,618100.0,618333.0,618528.0,618755.0,618901.0,618976.0,618943.0,618740.0,618420.0,617868.0,617254.0,616501.0,615715.0,614910.0,614135.0,613452.0,612807.0,612263.0,611794.0,611378.0,611058.0,610774.0,610415.0,610119.0,609907.0,609715.0,609540.0,609359.0,609285.0,609243.0,609160.0,609056.0,609035.0,608944.0,608916.0,608773.0,608668.0,608559.0,608397.0,608280.0,608188.0,608074.0,607919.0,607913.0,607885.0,607861.0,607913.0,607897.0,607912.0,607971.0,608105.0,608156.0,608303.0,608343.0,608448.0,608623.0,608658.0,608755.0,608978.0,609085.0,609172.0,609342.0,609462.0,609610.0,609660.0,609849.0,609982.0,610074.0,610267.0,610419.0,610575.0,610642.0,610884.0,610978.0,611119.0,611294.0,611479.0,611706.0,611838.0,612043.0,612199.0,612339.0,612540.0,612743.0,612877.0,613080.0,613261.0,613462.0,613682.0,613825.0,614071.0,614261.0,614404.0,614605.0,614777.0,615030.0,615251.0,615451.0,615628.0,615893.0,616085.0,616199.0,616289.0,616272.0,615993.0,615596.0,615032.0,614312.0,613610.0,612792.0,611982.0,611219.0,610591.0,610004.0,609495.0,608982.0,608572.0,608279.0,608059.0,607749.0,607553.0,607399.0,607235.0,607228.0,607142.0,607110.0,607085.0,607098.0,607069.0,607007.0,607036.0,606973.0,606927.0,606792.0,606736.0,606646.0,606483.0,606330.0,606305.0,606265.0,606264.0,606240.0,606264.0,606309.0,606348.0,606448.0,606507.0,606635.0,606741.0,606842.0,607026.0,607137.0,607314.0,607427.0,607567.0,607740.0,607879.0,608064.0,608185.0,608347.0,608509.0,608723.0,608839.0,608986.0,609137.0,609347.0,609442.0,609573.0,609715.0,609883.0,610054.0,610289.0,610395.0,610611.0,610749.0,610883.0,611086.0,611320.0,611503.0,611640.0,611865.0,612041.0,612323.0,612514.0,612702.0,612929.0,613139.0,613392.0,613605.0,613772.0,613789.0,613759.0,613460.0,613069.0,612481.0,611848.0,611152.0,610310.0,609615.0,608932.0,608344.0,607808.0,607404.0,607025.0,606739.0,606537.0,606350.0,606204.0,606083.0,606004.0,605972.0,606041.0,606027.0,606099.0,606069.0,606174.0,606197.0,606207.0,606171.0,606147.0,606085.0,606057.0,605973.0,605862.0,605839.0,605765.0,605711.0,605689.0,605649.0,605760.0,605762.0,605898.0,606320.0,604453.0,606130.0,606280.0,606470.0,606521.0,606713.0,606920.0,607069.0,607337.0,607480.0,607639.0,607767.0,607987.0,608135.0,608307.0,608458.0,608661.0,608861.0,608997.0,609163.0,609337.0,609458.0,609661.0,609862.0,609976.0,610211.0,610361.0,610574.0,610759.0,610951.0,611099.0,611334.0,611498.0,611673.0,611823.0,612084.0,612292.0,612508.0,612714.0,612970.0,613172.0,613434.0,613647.0,613914.0,614134.0,614241.0,614335.0,614209.0,613975.0,613512.0,612956.0,612334.0,611685.0,610973.0,610220.0,609625.0,609105.0,608607.0,608184.0,607889.0,607630.0,607509.0,607288.0,607258.0,607206.0,607192.0,607135.0,607200.0,607328.0,607374.0,607461.0,607519.0,607620.0,607663.0,607665.0,607651.0,607703.0,607580.0,607574.0,607478.0,607404.0,607370.0,607315.0,607347.0,607376.0,607427.0,607424.0,607580.0,607658.0,607738.0,607910.0,608092.0,608257.0,608448.0,608597.0,608750.0,609002.0,609843.0,610366.0,610175.0,610692.0,608104.0,610125.0,610385.0,610497.0,610721.0,610923.0,611092.0,611291.0,611464.0,611631.0,611881.0,611992.0,612184.0,612395.0,612561.0,612801.0,613015.0,613166.0,613356.0,613601.0,613799.0,614042.0,614258.0,614418.0,614582.0,614801.0,615021.0,615240.0,615527.0,615691.0,615917.0,616156.0,616407.0,616633.0,616914.0,617187.0,617408.0,617606.0,617745.0,617703.0,617523.0,617187.0,616693.0,616076.0,615313.0,614567.0,613851.0,613078.0,612396.0,611839.0,611306.0,610971.0,610511.0,610217.0,610000.0,609874.0,609688.0,609601.0,609539.0,609435.0,609459.0,609441.0,609483.0,609488.0,609548.0,609572.0,609542.0,609530.0,609584.0,609458.0,609422.0,609326.0,609272.0,609190.0,609123.0,609075.0,609040.0,609083.0,609060.0,609058.0,609183.0,609311.0,609406.0,609533.0,609626.0,609722.0,609888.0,610023.0,610231.0,610432.0,610595.0,610767.0,610978.0,611106.0,611302.0,611453.0,611625.0,611888.0,612006.0,612143.0,613661.0,613431.0,612761.0,612948.0,612966.0,613190.0,613359.0,613567.0,613771.0,613984.0,614158.0,614356.0,614530.0,614727.0,614906.0,615091.0,615309.0,615532.0,615686.0,615853.0,616155.0,616354.0,616589.0,616737.0,617050.0,617293.0,617518.0,617564.0,617642.0,617422.0,617139.0,616668.0,616059.0,615446.0,614701.0,613945.0,613231.0,612514.0,611988.0,611513.0,611113.0,610708.0,610522.0,610294.0,610099.0,610012.0,609889.0,609819.0,609791.0,609820.0,609833.0,609899.0,609930.0,609992.0,610037.0,610046.0,610013.0,609992.0,609991.0,609960.0,609837.0,609838.0,609802.0,609648.0,609692.0,609683.0,609703.0,609709.0,609817.0,609819.0,609928.0,609981.0,610145.0,610289.0,610397.0,610577.0,610713.0,610838.0,611070.0,611251.0,611494.0,611605.0,611816.0,612020.0,612229.0,612347.0,612603.0,612728.0,612945.0,613066.0,613369.0,613470.0,613639.0,613809.0,614025.0,615470.0,615537.0,615613.0,615975.0,614336.0,614744.0,615515.0,616730.0,617245.0,615822.0,616059.0,616218.0,616434.0,616634.0,616841.0,617081.0,617289.0,617498.0,617690.0,617719.0,617704.0,617454.0,617127.0,616585.0,615967.0,615369.0,614703.0,614044.0,613371.0,612738.0,612202.0,611777.0,611467.0,611153.0,610935.0,610770.0,610666.0,610574.0,610466.0,610402.0,610511.0,610538.0,610577.0,610651.0,610704.0,610739.0,610731.0,610663.0,610648.0,610580.0,610550.0,610404.0,610324.0,610176.0,610115.0,610063.0,610005.0,609956.0,609916.0,609919.0,609878.0,610006.0,610057.0,610193.0,610278.0,610441.0,610438.0,610658.0,610766.0,610916.0,611030.0,611147.0,611336.0,611539.0,611681.0,611831.0,611978.0,612184.0,612252.0,612427.0,612563.0,612656.0,612821.0,612991.0,613080.0,613226.0,613368.0,613486.0,613695.0,613874.0,614025.0,614183.0,614465.0,614580.0,614764.0,614959.0,615063.0,615067.0,615495.0,615223.0,614933.0,614356.0,613112.0,611302.0,610615.0,610036.0,609343.0,610013.0,609534.0,609319.0,608837.0,608568.0,608363.0,608250.0,608134.0,608105.0,608077.0,608104.0,608121.0,608216.0,608183.0,608320.0,608393.0,608379.0,608446.0,608477.0,608377.0,608347.0,608269.0,608156.0,608122.0,608069.0,607974.0,607951.0,608023.0,607992.0,608037.0,608106.0,608216.0,608265.0,608388.0,608573.0,608677.0,608852.0,608982.0,609162.0,609335.0,609443.0,609626.0,609813.0,610030.0,610194.0,610387.0,610538.0,610757.0,610905.0,611070.0,611240.0,611320.0,611521.0,611712.0,611846.0,611950.0,612160.0,612342.0,612532.0,612722.0,612832.0,612991.0,613221.0,613425.0,613637.0,613819.0,613948.0,613928.0,613781.0,613378.0,612874.0,612254.0,611597.0,610888.0,610059.0,609280.0,608589.0,607982.0,607414.0,606943.0,606582.0,606264.0,605930.0,605815.0,605540.0,605457.0,605310.0,605212.0,605204.0,605177.0,605185.0,605269.0,605235.0,605240.0,605250.0,603845.0,605219.0,603746.0,605065.0,605381.0,605316.0,605400.0,604302.0,603972.0,604693.0,604702.0,604625.0,604727.0,604815.0,604895.0,605002.0,605136.0,605341.0,605439.0,605624.0,605773.0,605916.0,606118.0,606327.0,606421.0,606590.0,606832.0,606933.0,607131.0,607334.0,607475.0,607628.0,607724.0,607976.0,608083.0,608209.0,608464.0,608599.0,608758.0,608923.0,609124.0,609267.0,609487.0,609643.0,609801.0,609997.0,610228.0,610402.0,610637.0,610905.0,611046.0,611221.0,611339.0,611374.0,611202.0,610775.0,610297.0,609658.0,608902.0,608173.0,607421.0,606666.0,605961.0,605342.0,604810.0,604286.0,603925.0,603624.0,603294.0,603016.0,602811.0,602619.0,602435.0,602342.0,602283.0,602296.0,602316.0,602325.0,602321.0,602290.0,602405.0,602344.0,602311.0,602292.0,602270.0,602241.0,602245.0,602132.0,602382.0,601973.0,602099.0,601997.0,602399.0,602812.0,602949.0,603116.0,603186.0,603270.0,603386.0,603300.0,603504.0,603691.0,603852.0,604052.0,604255.0,604447.0,604678.0,604901.0,605013.0,605221.0,605442.0,605616.0,605799.0,605971.0,606212.0,606393.0,606574.0,606783.0,606968.0,607171.0,607449.0,607617.0,607776.0,607994.0,608156.0,608392.0,608586.0,608759.0,609053.0,609250.0,609455.0,609690.0,609943.0,610207.0,610486.0,610620.0,610814.0,610786.0,610631.0,610285.0,609765.0,609172.0,608472.0,607745.0,606965.0,606257.0,605627.0,605101.0,604628.0,604247.0,603903.0,603635.0,603385.0,603168.0,602945.0,602782.0,602710.0,602608.0,602569.0,602574.0,602630.0,602594.0,602662.0,602722.0,602733.0,602739.0,602758.0,602761.0,602758.0,602775.0,602703.0,602737.0,602716.0,602735.0,602815.0,602863.0,602991.0,603108.0,603195.0,603322.0,603496.0,603621.0,603907.0,604018.0,604231.0,604353.0,604608.0,604781.0,605032.0,605101.0,606344.0,606573.0,605858.0,607062.0,606108.0,606384.0,606565.0,606747.0,606979.0,607165.0,607333.0,607535.0,607800.0,608070.0,608202.0,608425.0,608611.0,608799.0,609020.0,609236.0,609429.0,609707.0,609887.0,610096.0,610299.0,610531.0,610762.0,611007.0,611218.0,611492.0,611710.0,611922.0,612162.0,612401.0,612708.0,612929.0,613081.0,613375.0,613387.0,613292.0,613016.0,612578.0,612007.0,611304.0,610554.0,609890.0,609180.0,608568.0,607907.0,607484.0,607105.0,606730.0,606517.0,606268.0,606055.0,605946.0,605795.0,605716.0,605627.0,605571.0,605603.0,605633.0,605667.0,605719.0,605705.0,605760.0,605730.0,605763.0,605749.0,605740.0,605646.0,605624.0,605533.0,605617.0,605589.0,605599.0,605651.0,605628.0,605683.0,605901.0,605970.0,606104.0,606228.0,606352.0,606518.0,606726.0,606828.0,607023.0,607189.0,607438.0,607619.0,607790.0,607962.0,608139.0,608328.0,608502.0,607636.0,609652.0,609699.0,610046.0,608077.0,608183.0,608489.0,608623.0,610105.0,610356.0,610585.0,610660.0,610886.0,611092.0,611320.0,611497.0,611715.0,611921.0,612117.0,612335.0,612524.0,612709.0,612895.0,613088.0,613364.0,613521.0,613730.0,613957.0,614154.0,614443.0,614643.0,614903.0,615053.0,615298.0,615486.0,615696.0,615718.0,615531.0,615273.0,614804.0,614244.0,613694.0,612988.0,612303.0,611728.0,611033.0,610513.0,610110.0,609782.0,609499.0,609227.0,609086.0,608954.0,608829.0,608730.0,608704.0,608695.0,608739.0,608812.0,608821.0,608936.0,608916.0,609022.0,609032.0,609037.0,609025.0,609006.0,609002.0,608952.0,608891.0,608855.0,608823.0,608877.0,608900.0,608938.0,608983.0,609060.0,609203.0,609306.0,609494.0,609614.0,609768.0,609906.0,610128.0,610334.0,610540.0,610656.0,610901.0,611130.0,611234.0,611432.0,611628.0,611790.0,612009.0,612177.0,612367.0,612534.0,612735.0,612925.0,613066.0,613284.0,613424.0,613639.0,614860.0,614972.0,615230.0,615260.0,614906.0,613427.0,615332.0,613864.0,615235.0,615493.0,615452.0,615818.0,616289.0,616523.0,616778.0,617027.0,617220.0,617504.0,617714.0,618016.0,618228.0,618366.0,618482.0,618496.0,618323.0,617973.0,617468.0,616852.0,616167.0,615483.0,614772.0,614078.0,613438.0,612867.0,612453.0,611992.0,611603.0,611334.0,611097.0,610847.0,610645.0,610503.0,610350.0,610247.0,610179.0,610154.0,610121.0,610105.0,610082.0,610035.0,609990.0,609962.0,609882.0,609839.0,609752.0,609683.0,609587.0,609519.0,609510.0,609499.0,609481.0,609443.0,609506.0,609597.0,609668.0,609674.0,609808.0,609933.0,610046.0,610198.0,610285.0,610471.0,610637.0,610750.0,610936.0,611102.0,611314.0,611525.0,611678.0,611839.0,612052.0,612242.0,612351.0,612581.0,612769.0,612844.0,613068.0,613189.0,613381.0,613530.0,613684.0,613814.0,613992.0,614208.0,614379.0,614574.0,614767.0,614985.0,615149.0,615395.0,615581.0,615765.0,615926.0,614754.0,616576.0,616720.0,617153.0,616352.0,615681.0,616944.0,616135.0,617683.0,618439.0,618512.0,618575.0,618622.0,618734.0,618762.0,618703.0,618469.0,618019.0,617475.0,616803.0,616023.0,615259.0,614464.0,613787.0,613125.0,612560.0,612023.0,611562.0,611219.0,610847.0,610515.0,610227.0,610081.0,609848.0,609710.0,609581.0,609525.0,609425.0,609417.0,609311.0,609304.0,609221.0,609122.0,609091.0,608943.0,608875.0,608811.0,608631.0,608467.0,608455.0,608298.0,608270.0,608233.0,608208.0,608141.0,608125.0,608110.0,608245.0,608217.0,608313.0,608435.0,608548.0,608605.0,608757.0,608819.0,608986.0,609054.0,609205.0,609342.0,609468.0,609622.0,609748.0,609940.0,610087.0,610242.0,610358.0,610486.0,610611.0,610790.0,610970.0,611088.0,611212.0,611327.0,611516.0,611672.0,611831.0,611961.0,612150.0,612231.0,612420.0,612545.0,612782.0,612916.0,613045.0,612580.0,613988.0,613042.0,612782.0,613051.0,614727.0,614471.0,614701.0,614923.0,615032.0,615119.0,615103.0,614865.0,614516.0,613931.0,613261.0,612537.0,611729.0,610936.0,610219.0,609488.0,608828.0,608292.0,607825.0,607434.0,607100.0,606810.0,606595.0,606397.0,606229.0,606067.0,605987.0,605883.0,605888.0,605893.0,605854.0,605885.0,605862.0,605843.0,605794.0,605636.0,605577.0,605502.0,605343.0,605243.0,605163.0,605071.0,605021.0,604963.0,604923.0,604889.0,604881.0,604889.0,605030.0,605056.0,605141.0,605267.0,605355.0,605451.0,605620.0,605766.0,605916.0,606042.0,606191.0,606345.0,606519.0,606611.0,606793.0,606939.0,607099.0,607271.0,607423.0,607544.0,607686.0,607865.0,607991.0,608200.0,608289.0,608495.0,608642.0,608739.0,608927.0,609084.0,609226.0,609390.0,609531.0,609757.0,609950.0,610084.0,610292.0,610489.0,610653.0,610859.0,611061.0,611235.0,611398.0,611442.0,611351.0,611081.0,610669.0,610936.0,610346.0,608310.0,606360.0,605857.0,606612.0,605408.0,605612.0,604331.0,604146.0,603806.0,603498.0,603297.0,603176.0,603025.0,602917.0,602817.0,602816.0,602846.0,602898.0,602896.0,602964.0,602930.0,602951.0,602910.0,602863.0,602782.0,602702.0,602531.0,602478.0,602347.0,602237.0,602228.0,602153.0,602145.0,602050.0,602137.0,602170.0,602304.0,602382.0,602505.0,602606.0,602738.0,602837.0,602962.0,603193.0,603440.0,603570.0,603783.0,603924.0,604131.0,604321.0,604443.0,604692.0,604864.0,604976.0,605223.0,605392.0,605595.0,605761.0,605945.0,606101.0,606279.0,606423.0,606621.0,606749.0,606975.0,607108.0,607309.0,607537.0,607759.0,607973.0,608139.0,608361.0,608542.0,608675.0,608596.0,608394.0,608008.0,607411.0,606752.0,605867.0,605067.0,604261.0,603460.0,602700.0,602096.0,601481.0,601012.0,600542.0,600173.0,599903.0,599662.0,599420.0,599267.0,599089.0,599010.0,598955.0,598945.0,598960.0,599181.0,598905.0,598943.0,598867.0,598848.0,598828.0,598663.0,598666.0,598500.0,598376.0,598323.0,598247.0,598194.0,598194.0,598196.0,598142.0,598171.0,598242.0,598326.0,598493.0,598587.0,598741.0,598882.0,599067.0,599225.0,599391.0,599552.0,599767.0,599947.0,600081.0,600334.0,600518.0,600649.0,600853.0,601002.0,601250.0,601413.0,601556.0,601695.0,601876.0,602074.0,602243.0,602429.0,602591.0,602818.0,602976.0,603151.0,603392.0,603545.0,603787.0,603966.0,604183.0,604440.0,604662.0,604882.0,605012.0,605054.0,604888.0,604551.0,604054.0,603379.0,602679.0,601878.0,601067.0,600299.0,599622.0,599012.0,598459.0,598118.0,597752.0,597411.0,597118.0,596969.0,596821.0,596621.0,596580.0,596512.0,596474.0,596462.0,596599.0,596587.0,596629.0,596644.0,596667.0,596713.0,596687.0,596683.0,596605.0,596621.0,596581.0,596561.0,596503.0,596517.0,596614.0,596586.0,596607.0,596654.0,596833.0,596956.0,597068.0,597282.0,597487.0,597216.0,598022.0,598033.0,598251.0,598426.0,598653.0,598877.0,599164.0,599306.0,599543.0,599800.0,599990.0,600200.0,600380.0,600572.0,600887.0,601032.0,601184.0,601386.0,601621.0,601821.0,602022.0,602232.0,602441.0,602674.0,602869.0,603063.0,603299.0,603541.0,603781.0,604045.0,604299.0,604629.0,604857.0,605058.0,605323.0,605397.0,605281.0,605035.0,604612.0,604008.0,603369.0,602641.0,601878.0,601162.0,600486.0,599863.0,599407.0,599013.0,598607.0,598341.0,598138.0,597920.0,597821.0,597759.0,597693.0,597626.0,597642.0,597717.0,597774.0,597904.0,597929.0,598024.0,598142.0,598099.0,598166.0,598200.0,598185.0,598152.0,598182.0,598155.0,598151.0,598236.0,598246.0,598321.0,598409.0,598533.0,598622.0,598771.0,598892.0,599105.0,599287.0,599534.0,599641.0,599971.0,600166.0,600379.0,600567.0,600815.0,601052.0,601361.0,601544.0,601763.0,601971.0,602224.0,602438.0,602668.0,602895.0,603060.0,603305.0,603577.0,603808.0,604093.0,604283.0,604551.0,604818.0,604975.0,605198.0,605463.0,605737.0,605952.0,606177.0,606442.0,606684.0,606977.0,607260.0,607448.0,607791.0,608046.0,608339.0,608570.0,608742.0,608793.0,608753.0,608418.0,607926.0,607398.0,606720.0,607060.0,606535.0,605860.0,605140.0,602085.0,599373.0,602699.0,602427.0,602213.0,602035.0,601949.0,601883.0,601813.0,601795.0,601900.0,601916.0,602027.0,602153.0,602223.0,602320.0,602428.0,602500.0,602510.0,602522.0,602530.0,602541.0,602578.0,602449.0,602504.0,602575.0,602606.0,602674.0,602705.0,602766.0,602887.0,602965.0,603189.0,603287.0,603462.0,603716.0,603944.0,604112.0,604321.0,604510.0,604724.0,604946.0,605164.0,605407.0,605534.0,605828.0,606056.0,606178.0,606420.0,606552.0,606808.0,606943.0,607118.0,607333.0,607537.0,607669.0,607847.0,608002.0,608249.0,608488.0,608706.0,608910.0,609191.0,609332.0,609620.0,609946.0,610135.0,610367.0,610648.0,610890.0,611051.0,611134.0,611061.0,610875.0,610480.0,609868.0,609363.0,608707.0,608121.0,607534.0,606983.0,606540.0,606148.0,605862.0,605617.0,605489.0,605372.0,605409.0,605723.0,605899.0,606130.0,606202.0,604886.0,604932.0,605942.0,605779.0,606585.0,606407.0,606502.0,607668.0,606434.0,606464.0,607729.0,606478.0,606494.0,606559.0,606581.0,606545.0,606610.0,606706.0,606824.0,606992.0,607180.0,607373.0,607605.0,607830.0,608078.0,608315.0,608545.0,608885.0,609050.0,609308.0,609574.0,609753.0,610077.0,610293.0,610515.0,610799.0,611032.0,611244.0,611508.0,611762.0,612003.0,612239.0,612476.0,612725.0,612973.0,613186.0,613385.0,613640.0,613879.0,614147.0,614406.0,614602.0,614850.0,615103.0,615376.0,615614.0,615873.0,616150.0,616430.0,616630.0,616822.0,616803.0,616707.0,616427.0,616014.0,615451.0,614807.0,614105.0,613412.0,612781.0,612137.0,611650.0,611245.0,610848.0,610517.0,610285.0,610061.0,609918.0,609743.0,609612.0,609562.0,609491.0,609458.0,609542.0,609578.0,609651.0,609668.0,609726.0,609739.0,609720.0,609765.0,609813.0,609774.0,609725.0,609694.0,610003.0,610023.0,610034.0,610057.0,608425.0,609130.0,609581.0,609512.0,610112.0,610273.0,610399.0,610622.0,610661.0,610850.0,611057.0,611200.0,611452.0,610338.0,611726.0,611929.0,612087.0,612261.0,612487.0,612629.0,612838.0,613015.0,613238.0,613389.0,613607.0,613736.0,613980.0,614180.0,614263.0,614500.0,614736.0,614891.0,615177.0,615338.0,615509.0,615704.0,615942.0,616130.0,616384.0,616579.0,616864.0,617097.0,617291.0,617510.0,617807.0,617967.0,618053.0,618027.0,617807.0,617503.0,616934.0,616372.0,615659.0,614931.0,614182.0,613512.0,612887.0,612320.0,611846.0,611554.0,611244.0,610966.0,610697.0,610461.0,610357.0,610191.0,610103.0,610020.0,610007.0,610036.0,610034.0,610069.0,610114.0,610059.0,610036.0,610082.0,610037.0,609999.0,609936.0,609858.0,609830.0,609809.0,609763.0,609757.0,609737.0,609739.0,609796.0,609868.0,610008.0,610131.0,610168.0,610367.0,610549.0,610626.0,610869.0,611033.0,611172.0,611404.0,611583.0,611751.0,611924.0,612096.0,612244.0,612425.0,612614.0,612823.0,612918.0,613358.0,613455.0,612258.0,613630.0,613497.0,615232.0,613894.0,615699.0,614473.0,614658.0,614857.0,615035.0,615220.0,615465.0,615647.0,615851.0,616039.0,616363.0,616516.0,616790.0,617021.0,617211.0,617414.0,617395.0,617433.0,617135.0,616764.0,616171.0,615508.0,614869.0,614078.0,613363.0,612747.0,612073.0,611545.0,611076.0,610728.0,610407.0,610162.0,610048.0,609919.0,609791.0,609704.0,609682.0,609676.0,609674.0,609731.0,609796.0,609848.0,609890.0,609992.0,609935.0,609933.0,609943.0,609921.0,609858.0,609783.0,609742.0,609725.0,609631.0,609680.0,609710.0,609802.0,609759.0,609871.0,610035.0,610062.0,610213.0,610349.0,610509.0,610680.0,610862.0,610993.0,611196.0,611347.0,611573.0,611681.0,611877.0,612099.0,612302.0,612481.0,612662.0,612784.0,613009.0,613171.0,613366.0,613556.0,613948.0,613879.0,614051.0,614195.0,614165.0,614916.0,614986.0,615159.0,616104.0,615342.0,615481.0,615742.0,615945.0,616150.0,616320.0,616609.0,616718.0,616877.0,616794.0,616533.0,616157.0,615581.0,615141.0,614437.0,613727.0,613125.0,612532.0,611943.0,611451.0,611086.0,610744.0,610504.0,610294.0,610151.0,610102.0,610024.0,610032.0,610026.0,610145.0,610180.0,610242.0,610315.0,610474.0,610494.0,610576.0,610593.0,610571.0,610581.0,610530.0,610501.0,610400.0,610392.0,610398.0,610418.0,610385.0,610467.0,610491.0,610644.0,610687.0,610812.0,610926.0,611124.0,611349.0,611463.0,611652.0,611936.0,612118.0,612335.0,612521.0,612738.0,612944.0,613114.0,613327.0,613526.0,613730.0,613917.0,614094.0,614332.0,614475.0,614761.0,614883.0,615119.0,615339.0,615538.0,615767.0,616015.0,616142.0,616426.0,616672.0,616855.0,617095.0,617343.0,617599.0,617768.0,617936.0,617950.0,617902.0,617618.0,617195.0,616670.0,615868.0,614519.0,614616.0,614589.0,614775.0,613061.0,613868.0,611791.0,611001.0,610922.0,610428.0,610448.0,611318.0,611263.0,611254.0,611241.0,611363.0,611393.0,611454.0,611566.0,611590.0,611740.0,611751.0,611836.0,611875.0,611845.0,611847.0,611857.0,611850.0,611900.0,611850.0,611896.0,611906.0,612016.0,612076.0,612168.0,612289.0,612422.0,612547.0,612724.0,612883.0,613123.0,613322.0,613528.0,613754.0,613935.0,614120.0,614366.0,614580.0,614716.0,614934.0,615145.0,615352.0,615534.0,615851.0,616006.0,616158.0,616332.0,616509.0,616730.0,616938.0,617159.0,617380.0,617557.0,617741.0,617972.0,618204.0,618428.0,618677.0,618923.0,619105.0,619279.0,619446.0,619481.0,619381.0,619171.0,618772.0,618278.0,617769.0,617133.0,616533.0,615952.0,615393.0,614994.0,614552.0,614239.0,613971.0,613731.0,613534.0,613388.0,613273.0,613206.0,613119.0,613114.0,613185.0,613319.0,613340.0,613435.0,614405.0,613514.0,613653.0,614751.0,614764.0,612225.0,613292.0,612135.0,612138.0,613456.0,613461.0,613502.0,613583.0,613606.0,613720.0,613775.0,613928.0,614081.0,614233.0,614339.0,614518.0,614645.0,614852.0,614996.0,615242.0,615422.0,615598.0,615771.0,615938.0,616120.0,616269.0,616463.0,616648.0,616875.0,617003.0,617231.0,617443.0,617600.0,617911.0,617974.0,618193.0,618328.0,618568.0,618718.0,618910.0,619118.0,619297.0,619495.0,619712.0,619906.0,620135.0,620362.0,620576.0,620748.0,620844.0,620836.0,620618.0,620258.0,619806.0,619227.0,618625.0,618025.0,617377.0,616804.0,616230.0,615816.0,615382.0,615065.0,614847.0,614597.0,614415.0,614230.0,614143.0,613938.0,613932.0,613925.0,613953.0,613996.0,614060.0,614086.0,614156.0,614139.0,614181.0,614153.0,614116.0,614104.0,614064.0,613986.0,613955.0,613960.0,613962.0,613957.0,613957.0,613971.0,614080.0,614096.0,614894.0,613821.0,615138.0,615178.0,614747.0,614882.0,615193.0,615187.0,615182.0,615086.0,616718.0,614315.0,615813.0,616052.0,616198.0,616388.0,616487.0,616649.0,616817.0,616967.0,617124.0,617258.0,617472.0,617597.0,617755.0,617940.0,618163.0,618224.0,618409.0,618613.0,618745.0,618935.0,619149.0,619317.0,619536.0,619714.0,619887.0,620128.0,620292.0,620463.0,620421.0,620337.0,620070.0,619605.0,619082.0,618484.0,617809.0,617158.0,616500.0,615919.0,615377.0,614935.0,614538.0,614198.0,613944.0,613721.0,613576.0,613436.0,613359.0,613314.0,613260.0,613279.0,613354.0,613341.0,613359.0,613442.0,613447.0,613429.0,613366.0,613269.0,613261.0,613146.0,613065.0,612943.0,612878.0,612793.0,612733.0,612797.0,612740.0,612775.0,612822.0,612849.0,612926.0,613058.0,613189.0,613310.0,613401.0,613566.0,613664.0,613789.0,613978.0,614168.0,614318.0,614517.0,614669.0,614813.0,614982.0,615129.0,615282.0,615400.0,615578.0,615738.0,615832.0,615957.0,616121.0,616264.0,616392.0,616573.0,616737.0,616910.0,617114.0,617015.0,617960.0,617788.0,617865.0,619325.0,619454.0,619548.0,619210.0,617990.0,617718.0,617199.0,616631.0,615975.0,615253.0,614546.0,613924.0,613334.0,612810.0,612367.0,611930.0,611631.0,611456.0,611136.0,610977.0,610822.0,610726.0,610633.0,610622.0,610641.0,610662.0,610722.0,610732.0,610751.0,610769.0,610733.0,610749.0,610622.0,610594.0,610550.0,610473.0,610342.0,610311.0,610319.0,610287.0,610256.0,610317.0,610306.0,610367.0,610435.0,610562.0,610668.0,610781.0,610924.0,611063.0,611274.0,611348.0,611445.0,611645.0,611804.0,611971.0,612123.0,612321.0,612443.0,612554.0,612801.0,612929.0,613117.0,613337.0,613424.0,613654.0,613753.0,613916.0,614043.0,614233.0,614383.0,614536.0,614709.0,614875.0,615086.0,615277.0,615475.0,615694.0,615886.0,616094.0,616312.0,616496.0,616755.0,616882.0,616947.0,616937.0,616711.0,616284.0,615796.0,615068.0,614349.0,613629.0,612851.0,612120.0,611468.0,611142.0,610730.0,610322.0,609696.0,607905.0,609129.0,608926.0,608746.0,608436.0,608275.0,608181.0,608163.0,608103.0,608107.0,608095.0,608073.0,608113.0,608014.0,607999.0,607923.0,607867.0,607751.0,607702.0,607595.0,607476.0,607471.0,607487.0,607450.0,607378.0,607419.0,607474.0,607581.0,607593.0,607706.0,607856.0,607946.0,608064.0,608149.0,608380.0,608510.0,608689.0,608854.0,608998.0,609165.0,609332.0,609514.0,609676.0,609808.0,609939.0,610108.0,610298.0,610421.0,610642.0,610756.0,610948.0,611075.0,611272.0,611440.0,611666.0,611809.0,611966.0,612207.0,612369.0,612547.0,612712.0,612881.0,613100.0,613324.0,613539.0,613754.0,614050.0,614215.0,614396.0,614583.0,614652.0,614553.0,614264.0,613805.0,613265.0,612568.0,611763.0,611016.0,610340.0,609611.0,608990.0,608748.0,608183.0,607789.0,607650.0,607706.0,607440.0,607525.0,605095.0,604931.0,606646.0,604747.0,605952.0,605936.0,605920.0,605926.0,605960.0,605933.0,605992.0,605874.0,605857.0,605804.0,605781.0,605675.0,605660.0,605539.0,605529.0,605567.0,605536.0,605575.0,605630.0,605669.0,605806.0,605894.0,605970.0,606083.0,606267.0,606491.0,606611.0,605982.0,604776.0,607091.0,607247.0,607405.0,607604.0,607764.0,607927.0,608175.0,608311.0,608475.0,608682.0,608837.0,609007.0,609145.0,609385.0,609546.0,609751.0,609837.0,610094.0,610281.0,610423.0,610647.0,610850.0,610916.0,611134.0,611367.0,611490.0,611661.0,611882.0,612075.0,612277.0,612481.0,612776.0,612929.0,613166.0,613323.0,613476.0,613586.0,613431.0,613126.0,612691.0,612019.0,611402.0,610693.0,609919.0,609199.0,608450.0,607884.0,607380.0,606869.0,606595.0,606312.0,606037.0\n","output_type":"stream"}]},{"cell_type":"code","source":"gsr_third_value = data_intervals_dataframe['gsr_data'].iloc[2]\nprint(gsr_third_value)\nprint(type(gsr_third_value))","metadata":{"execution":{"iopub.status.busy":"2024-11-15T03:34:25.546413Z","iopub.execute_input":"2024-11-15T03:34:25.547245Z","iopub.status.idle":"2024-11-15T03:34:25.964478Z","shell.execute_reply.started":"2024-11-15T03:34:25.547186Z","shell.execute_reply":"2024-11-15T03:34:25.962832Z"},"trusted":true},"execution_count":1,"outputs":[{"traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mNameError\u001b[0m                                 Traceback (most recent call last)","Cell \u001b[0;32mIn[1], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m gsr_third_value \u001b[38;5;241m=\u001b[39m \u001b[43mdata_intervals_dataframe\u001b[49m[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mgsr_data\u001b[39m\u001b[38;5;124m'\u001b[39m]\u001b[38;5;241m.\u001b[39miloc[\u001b[38;5;241m2\u001b[39m]\n\u001b[1;32m      2\u001b[0m \u001b[38;5;28mprint\u001b[39m(gsr_third_value)\n\u001b[1;32m      3\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;28mtype\u001b[39m(gsr_third_value))\n","\u001b[0;31mNameError\u001b[0m: name 'data_intervals_dataframe' is not defined"],"ename":"NameError","evalue":"name 'data_intervals_dataframe' is not defined","output_type":"error"}]},{"cell_type":"markdown","source":"# TODO EDIT HERE 15 NOV 1223 AM","metadata":{}},{"cell_type":"code","source":"# import numpy as np\n# import pandas as pd\n# # from scipy.signal import morlet\n# from scipy.stats import kurtosis\n# import pywt\n\n# # Function to convert the string in 'gsr_data' to a list of floats\n# def convert_gsr_data_string_to_list(gsr_data_str):\n#     \"\"\"\n#     Convert a string of comma-separated numbers into a list of floats.\n#     \"\"\"\n#     return list(map(float, gsr_data_str.split(',')))\n\n# # Example function to extract CWT coefficients and frequencies from GSR data\n# def extract_cwt_features(gsr_data, sampling_rate=4, wavelet='morlet'):\n#     \"\"\"\n#     Perform Continuous Wavelet Transform on GSR data and return coefficients and frequencies.\n    \n#     Parameters:\n#     - gsr_data: List or array of GSR signal values\n#     - sampling_rate: The sampling frequency of the GSR data (default is 100Hz)\n#     - wavelet: Type of wavelet to use (default is 'morlet')\n    \n#     Returns:\n#     - cwt_coefficients: The coefficients resulting from the CWT\n#     - frequencies: The corresponding frequencies used for the CWT\n#     \"\"\"\n#     # Ensure gsr_data is a numpy array\n#     gsr_signal = np.array(gsr_data)\n#     scales = np.arange(1,271 )\n    \n#     # Generate a time array for the signal (assuming uniform sampling)\n# #     time_points = np.arange(len(gsr_signal)) / sampling_rate\n\n    \n#     # Perform CWT using the specified wavelet (e.g., Morlet)\n#     cwt_coefficients, cwt_frequencies = pywt.cwt(gsr_signal , scales, 'morl')\n    \n    \n#     return cwt_coefficients\n\n# # Function to extract features from CWT coefficients\n# def extract_cwt_features_from_coefficients(cwt_coefficients):\n#     \"\"\"\n#     Extract time-domain features from CWT coefficients.\n    \n#     Parameters:\n#     - cwt_coefficients: 2D array (scales x time points)\n    \n#     Returns:\n#     - A dictionary containing mean, variance, max, and kurtosis of the coefficients across time\n#     \"\"\"\n#     # Take the magnitude of the complex CWT coefficients (optional)\n# #     cwt_coefficients_mag = np.abs(cwt_coefficients)\n    \n#     # Extract features across scales\n#     mean_coefficients = np.mean(cwt_coefficients, axis=1)  # Mean across time for each scale\n#     variance_coefficients = np.var(cwt_coefficients, axis=1)  # Variance across time for each scale\n#     max_coefficients = np.max(cwt_coefficients, axis=1)  # Max value across time for each scale\n#     kurtosis_coefficients = kurtosis(cwt_coefficients, axis=1)  # Kurtosis across time for each scale\n    \n#     return {\n#         'mean_coefficients': mean_coefficients,\n#         'variance_coefficients': variance_coefficients,\n#         'max_coefficients': max_coefficients,\n#         'kurtosis_coefficients': kurtosis_coefficients\n#     }\n\n\n# # TODO EDIT HERE\n# # Apply CWT to the GSR data and extract features\n# def extract_features_for_all_rows(df):\n#     features = []\n    \n#     for _, row in df.iterrows():\n#         gsr_data_str = row['gsr_data']\n        \n#         # Convert the GSR data string into a list of numbers\n#         gsr_data = convert_gsr_data_string_to_list(gsr_data_str)\n        \n#         # Extract CWT coefficients for the GSR data\n#         cwt_coefficients = extract_cwt_features(gsr_data)\n        \n#         # Extract features from the CWT coefficients\n#         cwt_features = extract_cwt_features_from_coefficients(cwt_coefficients)\n        \n#         # Append features along with subject_id, video_id, and arousal_valence_label\n#         feature_dict = {\n#             'subject_id': row['subject_id'],\n#             'video_id': row['video_id'],\n#             'arousal_valence_label': row['arousal_valence_label'],\n#             'mean_coefficients': cwt_features['mean_coefficients'],\n#             'variance_coefficients': cwt_features['variance_coefficients'],\n#             'max_coefficients': cwt_features['max_coefficients'],\n#             'kurtosis_coefficients': cwt_features['kurtosis_coefficients']\n#         }\n#         features.append(feature_dict)\n    \n#     # Convert the list of features to a DataFrame\n#     gsr_features = pd.DataFrame(features)\n#     return gsr_features\n\n\n\n# # Extract features from all rows in the dataframe\n# gsr_features = extract_features_for_all_rows(data_intervals_dataframe)\n\n# # Verify the result\n# print(gsr_features.head())\n\n","metadata":{"execution":{"iopub.status.busy":"2024-11-14T16:18:46.582743Z","iopub.execute_input":"2024-11-14T16:18:46.583724Z","iopub.status.idle":"2024-11-14T16:19:59.583427Z","shell.execute_reply.started":"2024-11-14T16:18:46.583680Z","shell.execute_reply":"2024-11-14T16:19:59.582430Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Extract CWT Features from GSR DATA - 15 NOV 1134 AM","metadata":{}},{"cell_type":"code","source":"# import numpy as np\n# import pandas as pd\n# import pywt  # Make sure to have PyWavelets installed: pip install PyWavelets\n\n# # Function to convert the string in 'gsr_data' to a list of floats\n# def convert_gsr_data_string_to_list(gsr_data_str):\n#     \"\"\"\n#     Convert a string of comma-separated numbers into a list of floats.\n#     \"\"\"\n#     return list(map(float, gsr_data_str.split(',')))\n\n# # Function to extract wavelet coefficients and flatten them\n# def extract_wavelet_features(signal, scales, wavelet='morl'):\n#     \"\"\"\n#     Extract wavelet coefficients from the signal using the specified scales and wavelet.\n    \n#     Parameters:\n#     - signal: List or array of numerical values representing the signal data\n#     - scales: Scales to be used in the wavelet transform\n#     - wavelet: Type of wavelet to use (default is 'morl')\n    \n#     Returns:\n#     - Flattened wavelet coefficients\n#     \"\"\"\n#     coefficients, _ = pywt.cwt(signal, scales, wavelet)\n#     flat_coefficients = coefficients.flatten()\n#     return flat_coefficients\n\n# # Set the scales for wavelet transform\n# scales = np.arange(1, 271)\n\n# # Prepare the feature matrix and labels\n# gsr_features = []\n# labels = []\n\n# # Iterate over each row in the dataframe to extract features and labels\n# for _, row in data_intervals_dataframe.iterrows():\n#     # Step 1: Get the GSR data and convert to list of floats\n#     gsr_data_str = row['gsr_data']\n#     gsr_data = convert_gsr_data_string_to_list(gsr_data_str)\n    \n#     # Step 2: Extract wavelet coefficients\n#     wavelet_features = extract_wavelet_features(gsr_data, scales)\n    \n#     # Append the features and label\n#     gsr_features.append(wavelet_features)\n#     labels.append(row['arousal_valence_label'])  # Assuming 'arousal_valence_label' is the target label\n\n# # # Step 3: Convert the features and labels into NumPy arrays\n# # gsr_features = np.array(gsr_features)\n# # labels = np.array(labels)\n\n# # # Step 4: Convert the features and labels into a DataFrame\n# # gsr_dataframe_features = pd.DataFrame(gsr_features)\n# # gsr_dataframe_features['label'] = labels\n\n# # # Verify the result\n# # print(gsr_dataframe_features.head())\n","metadata":{"execution":{"iopub.status.busy":"2024-11-15T03:35:01.284897Z","iopub.execute_input":"2024-11-15T03:35:01.285317Z","iopub.status.idle":"2024-11-15T03:36:38.033278Z","shell.execute_reply.started":"2024-11-15T03:35:01.285278Z","shell.execute_reply":"2024-11-15T03:36:38.031733Z"},"trusted":true},"execution_count":3,"outputs":[{"traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mValueError\u001b[0m                                Traceback (most recent call last)","Cell \u001b[0;32mIn[3], line 50\u001b[0m\n\u001b[1;32m     47\u001b[0m     labels\u001b[38;5;241m.\u001b[39mappend(row[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124marousal_valence_label\u001b[39m\u001b[38;5;124m'\u001b[39m])  \u001b[38;5;66;03m# Assuming 'arousal_valence_label' is the target label\u001b[39;00m\n\u001b[1;32m     49\u001b[0m \u001b[38;5;66;03m# Step 3: Convert the features and labels into NumPy arrays\u001b[39;00m\n\u001b[0;32m---> 50\u001b[0m gsr_features \u001b[38;5;241m=\u001b[39m \u001b[43mnp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43marray\u001b[49m\u001b[43m(\u001b[49m\u001b[43mgsr_features\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m     51\u001b[0m labels \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39marray(labels)\n\u001b[1;32m     53\u001b[0m \u001b[38;5;66;03m# Step 4: Convert the features and labels into a DataFrame\u001b[39;00m\n","\u001b[0;31mValueError\u001b[0m: setting an array element with a sequence. The requested array has an inhomogeneous shape after 1 dimensions. The detected shape was (2336,) + inhomogeneous part."],"ename":"ValueError","evalue":"setting an array element with a sequence. The requested array has an inhomogeneous shape after 1 dimensions. The detected shape was (2336,) + inhomogeneous part.","output_type":"error"}]},{"cell_type":"code","source":"# Function to extract summary features (mean, variance, max) from wavelet coefficients\ndef extract_summary_wavelet_features(signal, scales, wavelet='morl'):\n    coefficients, _ = pywt.cwt(signal, scales, wavelet)\n    mean_coefficients = np.mean(coefficients, axis=1)\n    variance_coefficients = np.var(coefficients, axis=1)\n    max_coefficients = np.max(coefficients, axis=1)\n    return np.concatenate([mean_coefficients, variance_coefficients, max_coefficients])\n\n# Prepare the feature matrix and labels\ngsr_features = []\nlabels = []\n\nfor _, row in data_intervals_dataframe.iterrows():\n    gsr_data_str = row['gsr_data']\n    gsr_data = convert_gsr_data_string_to_list(gsr_data_str)\n    wavelet_features = extract_summary_wavelet_features(gsr_data, scales)\n    gsr_features.append(wavelet_features)\n    labels.append(row['arousal_valence_label'])\n\n# Convert the features and labels into a DataFrame\ngsr_dataframe_features = pd.DataFrame(gsr_features)\ngsr_dataframe_features['label'] = labels\n\nprint(gsr_dataframe_features.head())\n","metadata":{"execution":{"iopub.status.busy":"2024-11-15T03:39:17.484997Z","iopub.execute_input":"2024-11-15T03:39:17.486189Z","iopub.status.idle":"2024-11-15T03:40:54.172047Z","shell.execute_reply.started":"2024-11-15T03:39:17.486130Z","shell.execute_reply":"2024-11-15T03:40:54.170951Z"},"trusted":true},"execution_count":4,"outputs":[{"name":"stdout","text":"            0           1           2            3            4            5  \\\n0 -489.900064  249.957626  695.798650  1269.989317  1932.865525  2512.369072   \n1 -371.298968  188.241130  525.770448   962.464232  1467.857592  1908.403496   \n2 -332.607841  169.239480  471.893956   864.626077  1319.076539  1715.661136   \n3 -372.088379  189.657275  527.273115   971.353328  1484.931226  1910.980927   \n4 -382.798504  194.297101  541.833674   994.344921  1519.422305  1980.394334   \n\n             6            7            8            9  ...           801  \\\n0  3035.143103  3931.854024  4497.474746  5534.682888  ...  9.222004e+05   \n1  2307.558460  2997.022780  3450.632849  4261.863915  ...  1.145031e+06   \n2  2070.769858  2677.494942  3060.500768  3755.133436  ...  1.059251e+06   \n3  2296.053767  3004.335749  3473.895496  4280.011442  ...  1.243630e+06   \n4  2393.632877  3090.700393  3528.040062  4328.128452  ...  1.185605e+06   \n\n            802           803           804           805           806  \\\n0  9.237927e+05  9.255680e+05  9.274040e+05  9.292090e+05  9.309539e+05   \n1  1.147379e+06  1.144002e+06  1.140174e+06  1.140890e+06  1.143464e+06   \n2  1.060781e+06  1.062591e+06  1.054079e+06  1.052712e+06  1.054688e+06   \n3  1.245969e+06  1.248440e+06  1.235894e+06  1.236140e+06  1.237884e+06   \n4  1.187782e+06  1.189970e+06  1.179381e+06  1.178421e+06  1.180561e+06   \n\n            807           808           809  label  \n0  9.268573e+05  9.285849e+05  9.076872e+05   AMVM  \n1  1.146107e+06  1.146324e+06  1.148582e+06   AMVL  \n2  1.056171e+06  1.058073e+06  1.059665e+06   AMVM  \n3  1.240125e+06  1.242307e+06  1.244429e+06   AMVM  \n4  1.182695e+06  1.184650e+06  1.186798e+06   AMVM  \n\n[5 rows x 811 columns]\n","output_type":"stream"}]},{"cell_type":"markdown","source":"# Checking GSR Dataframe Features","metadata":{}},{"cell_type":"code","source":"gsr_dataframe_features.shape","metadata":{"execution":{"iopub.status.busy":"2024-11-15T03:41:32.405218Z","iopub.execute_input":"2024-11-15T03:41:32.405999Z","iopub.status.idle":"2024-11-15T03:41:32.414101Z","shell.execute_reply.started":"2024-11-15T03:41:32.405955Z","shell.execute_reply":"2024-11-15T03:41:32.412965Z"},"trusted":true},"execution_count":5,"outputs":[{"execution_count":5,"output_type":"execute_result","data":{"text/plain":"(2336, 811)"},"metadata":{}}]},{"cell_type":"code","source":"# Save the DataFrame to a CSV file\ngsr_dataframe_features.to_csv('gsr_cwt_features.csv', index=False)\n\nprint(\"Data saved to gsr_cwt_features.csv\")\n\n# Specify the path to the CSV file\nfile_path = '/kaggle/working/gsr_cwt_features.csv'\n\n# Read the CSV file into a DataFrame\ngsr_cwt_features = pd.read_csv(file_path)","metadata":{"execution":{"iopub.status.busy":"2024-11-15T03:44:41.652124Z","iopub.execute_input":"2024-11-15T03:44:41.653131Z","iopub.status.idle":"2024-11-15T03:44:46.525772Z","shell.execute_reply.started":"2024-11-15T03:44:41.653084Z","shell.execute_reply":"2024-11-15T03:44:46.524844Z"},"trusted":true},"execution_count":6,"outputs":[{"name":"stdout","text":"Data saved to gsr_cwt_features.csv\n","output_type":"stream"}]},{"cell_type":"markdown","source":"# Load the GSR CWT FEATURES EXTRACTED CSV File ","metadata":{}},{"cell_type":"code","source":"# Specify the path to the CSV file\nfile_path = '/kaggle/input/gsr-cwt-features-extracted/gsr_cwt_features.csv'\n\n# Read the CSV file into a DataFrame\ngsr_dataframe_features_extracted = pd.read_csv(file_path)\n\nprint(gsr_dataframe_features_extracted.shape)\nprint(\"====================================\\n\")\nprint(gsr_dataframe_features_extracted.head())","metadata":{"execution":{"iopub.status.busy":"2024-11-15T03:54:36.620645Z","iopub.execute_input":"2024-11-15T03:54:36.621879Z","iopub.status.idle":"2024-11-15T03:54:37.211277Z","shell.execute_reply.started":"2024-11-15T03:54:36.621818Z","shell.execute_reply":"2024-11-15T03:54:37.210191Z"},"trusted":true},"execution_count":9,"outputs":[{"name":"stdout","text":"(2336, 811)\n====================================\n\n            0           1           2            3            4            5  \\\n0 -489.900064  249.957626  695.798650  1269.989317  1932.865525  2512.369072   \n1 -371.298968  188.241130  525.770448   962.464232  1467.857592  1908.403496   \n2 -332.607841  169.239480  471.893956   864.626077  1319.076539  1715.661136   \n3 -372.088379  189.657275  527.273115   971.353328  1484.931226  1910.980927   \n4 -382.798504  194.297101  541.833674   994.344921  1519.422305  1980.394334   \n\n             6            7            8            9  ...           801  \\\n0  3035.143103  3931.854024  4497.474746  5534.682888  ...  9.222004e+05   \n1  2307.558460  2997.022780  3450.632849  4261.863915  ...  1.145031e+06   \n2  2070.769858  2677.494942  3060.500768  3755.133436  ...  1.059251e+06   \n3  2296.053767  3004.335749  3473.895496  4280.011442  ...  1.243630e+06   \n4  2393.632877  3090.700393  3528.040062  4328.128452  ...  1.185605e+06   \n\n            802           803           804           805           806  \\\n0  9.237927e+05  9.255680e+05  9.274040e+05  9.292090e+05  9.309539e+05   \n1  1.147379e+06  1.144002e+06  1.140174e+06  1.140890e+06  1.143464e+06   \n2  1.060781e+06  1.062591e+06  1.054079e+06  1.052712e+06  1.054688e+06   \n3  1.245969e+06  1.248440e+06  1.235894e+06  1.236140e+06  1.237884e+06   \n4  1.187782e+06  1.189970e+06  1.179381e+06  1.178421e+06  1.180561e+06   \n\n            807           808           809  label  \n0  9.268573e+05  9.285849e+05  9.076872e+05   AMVM  \n1  1.146107e+06  1.146324e+06  1.148582e+06   AMVL  \n2  1.056171e+06  1.058073e+06  1.059665e+06   AMVM  \n3  1.240125e+06  1.242307e+06  1.244429e+06   AMVM  \n4  1.182695e+06  1.184650e+06  1.186798e+06   AMVM  \n\n[5 rows x 811 columns]\n","output_type":"stream"}]},{"cell_type":"markdown","source":"# Concatenate the PPG and GSR Features Extracted as a dataframe","metadata":{}},{"cell_type":"code","source":"ppg_all_features = pd.read_csv('/kaggle/input/ppg-extracted-features/ppg_all_features.csv')\n# gsr_dataframe_features_extracted\nprint(ppg_all_features.head())\nprint(ppg_all_features.columns)","metadata":{"execution":{"iopub.status.busy":"2024-11-15T03:57:14.939294Z","iopub.execute_input":"2024-11-15T03:57:14.939732Z","iopub.status.idle":"2024-11-15T03:57:14.991558Z","shell.execute_reply.started":"2024-11-15T03:57:14.939690Z","shell.execute_reply":"2024-11-15T03:57:14.990501Z"},"trusted":true},"execution_count":11,"outputs":[{"name":"stdout","text":"            mean           var         median            max            min  \\\n0  523505.254326  7.475432e+08  511816.485714  612290.528571  503611.914286   \n1  545864.656894  3.998819e+08  545232.657143  608431.714286  508410.371429   \n2  610096.347869  2.331632e+07  610328.371429  621036.285714  596463.114286   \n3  561703.954208  9.843148e+08  547563.742857  641537.542857  519073.514286   \n4  529994.326120  8.406916e+07  528735.542857  551489.257143  512744.657143   \n\n           range       rmssd        sdsd  nni_50    pnni_50  ...  \\\n0  108678.614286  228.366212  224.163580  1597.0  72.557928  ...   \n1  100021.342857  366.326098  366.382285  2609.0  84.134150  ...   \n2   24573.171429  297.220224  297.268152  2628.0  84.746856  ...   \n3  122464.028571  414.100720  413.899262  2643.0  85.230571  ...   \n4   38744.600000  234.629280  234.543208  2354.0  78.440520  ...   \n\n            vlf            lf            hf       tot_pow  lf_hf_ratio  \\\n0  0.000000e+00  6.147186e+07  3.030959e+05  6.177495e+07   202.813259   \n1  1.656730e+07  1.274268e+08  3.504205e+07  1.790361e+08     3.636396   \n2  2.496412e+06  9.414860e+06  1.981257e+06  1.389253e+07     4.751964   \n3  1.208835e+08  1.102812e+08  2.583060e+07  2.569952e+08     4.269400   \n4  1.917388e+06  1.034756e+07  1.214425e+07  2.440920e+07     0.852055   \n\n   peak_vlf   peak_lf   peak_hf      lf_nu      hf_nu  \n0  0.000000  0.044944  0.224719  99.509355   0.490645  \n1  0.032000  0.128000  0.160000  78.431525  21.568475  \n2  0.032000  0.128000  0.160000  82.614633  17.385367  \n3  0.032000  0.096000  0.160000  81.022508  18.977492  \n4  0.033058  0.132231  0.165289  46.005921  53.994079  \n\n[5 rows x 31 columns]\nIndex(['mean', 'var', 'median', 'max', 'min', 'range', 'rmssd', 'sdsd',\n       'nni_50', 'pnni_50', 'nni_20', 'pnni_20', 'avg_hr', 'std_hr', 'min_hr',\n       'max_hr', 'energy', 'abs_sum_diff', 'subject_id', 'video_id',\n       'arousal_valence_label', 'vlf', 'lf', 'hf', 'tot_pow', 'lf_hf_ratio',\n       'peak_vlf', 'peak_lf', 'peak_hf', 'lf_nu', 'hf_nu'],\n      dtype='object')\n","output_type":"stream"}]},{"cell_type":"code","source":"print(gsr_dataframe_features_extracted.columns)\n\n# drop the label column in gsr_dataframe_features_extracted \n#  and concatenate gsr_dataframe_features_extracted dataframe and ppg_all_features dataframe","metadata":{"execution":{"iopub.status.busy":"2024-11-15T03:57:44.083124Z","iopub.execute_input":"2024-11-15T03:57:44.083556Z","iopub.status.idle":"2024-11-15T03:57:44.090430Z","shell.execute_reply.started":"2024-11-15T03:57:44.083516Z","shell.execute_reply":"2024-11-15T03:57:44.089130Z"},"trusted":true},"execution_count":12,"outputs":[{"name":"stdout","text":"Index(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9',\n       ...\n       '801', '802', '803', '804', '805', '806', '807', '808', '809', 'label'],\n      dtype='object', length=811)\n","output_type":"stream"}]},{"cell_type":"code","source":"# Drop the 'label' column from gsr_dataframe_features_extracted and save as gsr_dataframe_features\ngsr_dataframe_features = gsr_dataframe_features_extracted.drop(columns=['label'])\n\n# Concatenate gsr_dataframe_features and ppg_all_features along columns (axis=1)\ncombined_features = pd.concat([gsr_dataframe_features, ppg_all_features], axis=1)\n\n# Display the first few rows to verify\nprint(combined_features.head())\n","metadata":{"execution":{"iopub.status.busy":"2024-11-15T04:04:05.628048Z","iopub.execute_input":"2024-11-15T04:04:05.628484Z","iopub.status.idle":"2024-11-15T04:04:05.654870Z","shell.execute_reply.started":"2024-11-15T04:04:05.628444Z","shell.execute_reply":"2024-11-15T04:04:05.653685Z"},"trusted":true},"execution_count":14,"outputs":[{"name":"stdout","text":"            0           1           2            3            4            5  \\\n0 -489.900064  249.957626  695.798650  1269.989317  1932.865525  2512.369072   \n1 -371.298968  188.241130  525.770448   962.464232  1467.857592  1908.403496   \n2 -332.607841  169.239480  471.893956   864.626077  1319.076539  1715.661136   \n3 -372.088379  189.657275  527.273115   971.353328  1484.931226  1910.980927   \n4 -382.798504  194.297101  541.833674   994.344921  1519.422305  1980.394334   \n\n             6            7            8            9  ...           vlf  \\\n0  3035.143103  3931.854024  4497.474746  5534.682888  ...  0.000000e+00   \n1  2307.558460  2997.022780  3450.632849  4261.863915  ...  1.656730e+07   \n2  2070.769858  2677.494942  3060.500768  3755.133436  ...  2.496412e+06   \n3  2296.053767  3004.335749  3473.895496  4280.011442  ...  1.208835e+08   \n4  2393.632877  3090.700393  3528.040062  4328.128452  ...  1.917388e+06   \n\n             lf            hf       tot_pow  lf_hf_ratio  peak_vlf   peak_lf  \\\n0  6.147186e+07  3.030959e+05  6.177495e+07   202.813259  0.000000  0.044944   \n1  1.274268e+08  3.504205e+07  1.790361e+08     3.636396  0.032000  0.128000   \n2  9.414860e+06  1.981257e+06  1.389253e+07     4.751964  0.032000  0.128000   \n3  1.102812e+08  2.583060e+07  2.569952e+08     4.269400  0.032000  0.096000   \n4  1.034756e+07  1.214425e+07  2.440920e+07     0.852055  0.033058  0.132231   \n\n    peak_hf      lf_nu      hf_nu  \n0  0.224719  99.509355   0.490645  \n1  0.160000  78.431525  21.568475  \n2  0.160000  82.614633  17.385367  \n3  0.160000  81.022508  18.977492  \n4  0.165289  46.005921  53.994079  \n\n[5 rows x 841 columns]\n","output_type":"stream"}]},{"cell_type":"markdown","source":"# Check shape of combined_features\n\n# 31 columns + 811 columns = 842 columns\n# 2336 rows","metadata":{}},{"cell_type":"code","source":"\ncombined_features.shape\n\n\n\n# Save the DataFrame to a CSV file\ncombined_features.to_csv('combined_features_ppg_gsr.csv', index=False)\n\nprint(\"Data saved to combined_features_ppg_gsr.csv\")\n\n# Specify the path to the CSV file\nfile_path = '/kaggle/working/combined_features_ppg_gsr.csv'\n\n# Read the CSV file into a DataFrame\ncombined_features_ppg_gsr = pd.read_csv(file_path)","metadata":{"execution":{"iopub.status.busy":"2024-11-15T04:08:06.066456Z","iopub.execute_input":"2024-11-15T04:08:06.066955Z","iopub.status.idle":"2024-11-15T04:08:11.127722Z","shell.execute_reply.started":"2024-11-15T04:08:06.066910Z","shell.execute_reply":"2024-11-15T04:08:11.126740Z"},"trusted":true},"execution_count":18,"outputs":[{"name":"stdout","text":"Data saved to combined_features_ppg_gsr.csv\n","output_type":"stream"}]},{"cell_type":"markdown","source":"# LOAD CSV HERE FOR COMBINED FEATURES PPG GSR CONTAINS LABELS  SUBJECT ID VIDEO ID AND AROUSAL_VALENCE_LABEL","metadata":{}},{"cell_type":"code","source":"combined_features_ppg_gsr.columns","metadata":{"execution":{"iopub.status.busy":"2024-11-15T04:10:14.150300Z","iopub.execute_input":"2024-11-15T04:10:14.151458Z","iopub.status.idle":"2024-11-15T04:10:14.159281Z","shell.execute_reply.started":"2024-11-15T04:10:14.151398Z","shell.execute_reply":"2024-11-15T04:10:14.158213Z"},"trusted":true},"execution_count":19,"outputs":[{"execution_count":19,"output_type":"execute_result","data":{"text/plain":"Index(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9',\n       ...\n       'vlf', 'lf', 'hf', 'tot_pow', 'lf_hf_ratio', 'peak_vlf', 'peak_lf',\n       'peak_hf', 'lf_nu', 'hf_nu'],\n      dtype='object', length=841)"},"metadata":{}}]},{"cell_type":"code","source":"# create a dataframe called features_for_model by dropping the columns 'subject_id' and 'video_id'\n#  from combined_features_ppg_gsr\n\n# use the arousal_valence_label as label for labels for training a classification model \n\n# Drop 'subject_id' and 'video_id' from combined_features_ppg_gsr to create features_for_model\nfeatures_for_model = combined_features_ppg_gsr.drop(columns=['subject_id', 'video_id'])\n\n# Extract the labels from 'arousal_valence_label' column for classification\nlabels = features_for_model['arousal_valence_label']\n\n# Drop 'arousal_valence_label' column from features_for_model to retain only the features\nfeatures_for_model = features_for_model.drop(columns=['arousal_valence_label'])\n\n# Display the first few rows to verify\nprint(features_for_model.head())\nprint(labels.head())\n","metadata":{"execution":{"iopub.status.busy":"2024-11-15T04:15:04.972759Z","iopub.execute_input":"2024-11-15T04:15:04.973876Z","iopub.status.idle":"2024-11-15T04:15:05.004004Z","shell.execute_reply.started":"2024-11-15T04:15:04.973816Z","shell.execute_reply":"2024-11-15T04:15:05.002956Z"},"trusted":true},"execution_count":20,"outputs":[{"name":"stdout","text":"            0           1           2            3            4            5  \\\n0 -489.900064  249.957626  695.798650  1269.989317  1932.865525  2512.369072   \n1 -371.298968  188.241130  525.770448   962.464232  1467.857592  1908.403496   \n2 -332.607841  169.239480  471.893956   864.626077  1319.076539  1715.661136   \n3 -372.088379  189.657275  527.273115   971.353328  1484.931226  1910.980927   \n4 -382.798504  194.297101  541.833674   994.344921  1519.422305  1980.394334   \n\n             6            7            8            9  ...           vlf  \\\n0  3035.143103  3931.854024  4497.474746  5534.682888  ...  0.000000e+00   \n1  2307.558460  2997.022780  3450.632849  4261.863915  ...  1.656730e+07   \n2  2070.769858  2677.494942  3060.500768  3755.133436  ...  2.496412e+06   \n3  2296.053767  3004.335749  3473.895496  4280.011442  ...  1.208835e+08   \n4  2393.632877  3090.700393  3528.040062  4328.128452  ...  1.917388e+06   \n\n             lf            hf       tot_pow  lf_hf_ratio  peak_vlf   peak_lf  \\\n0  6.147186e+07  3.030959e+05  6.177495e+07   202.813259  0.000000  0.044944   \n1  1.274268e+08  3.504205e+07  1.790361e+08     3.636396  0.032000  0.128000   \n2  9.414860e+06  1.981257e+06  1.389253e+07     4.751964  0.032000  0.128000   \n3  1.102812e+08  2.583060e+07  2.569952e+08     4.269400  0.032000  0.096000   \n4  1.034756e+07  1.214425e+07  2.440920e+07     0.852055  0.033058  0.132231   \n\n    peak_hf      lf_nu      hf_nu  \n0  0.224719  99.509355   0.490645  \n1  0.160000  78.431525  21.568475  \n2  0.160000  82.614633  17.385367  \n3  0.160000  81.022508  18.977492  \n4  0.165289  46.005921  53.994079  \n\n[5 rows x 838 columns]\n0    AMVM\n1    AMVL\n2    AMVM\n3    AMVM\n4    AMVM\nName: arousal_valence_label, dtype: object\n","output_type":"stream"}]},{"cell_type":"code","source":"features_for_model.columns","metadata":{"execution":{"iopub.status.busy":"2024-11-15T04:15:17.823162Z","iopub.execute_input":"2024-11-15T04:15:17.823615Z","iopub.status.idle":"2024-11-15T04:15:17.830978Z","shell.execute_reply.started":"2024-11-15T04:15:17.823571Z","shell.execute_reply":"2024-11-15T04:15:17.829860Z"},"trusted":true},"execution_count":21,"outputs":[{"execution_count":21,"output_type":"execute_result","data":{"text/plain":"Index(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9',\n       ...\n       'vlf', 'lf', 'hf', 'tot_pow', 'lf_hf_ratio', 'peak_vlf', 'peak_lf',\n       'peak_hf', 'lf_nu', 'hf_nu'],\n      dtype='object', length=838)"},"metadata":{}}]},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"features_for_model.to_csv('/kaggle/working/features_for_model.csv')\n\nprint(\"Data saved to features_for_model.csv\")\n\n# LOAD THE CSV ","metadata":{"execution":{"iopub.status.busy":"2024-11-15T04:18:41.449741Z","iopub.execute_input":"2024-11-15T04:18:41.450778Z","iopub.status.idle":"2024-11-15T04:18:45.893153Z","shell.execute_reply.started":"2024-11-15T04:18:41.450727Z","shell.execute_reply":"2024-11-15T04:18:45.891884Z"},"trusted":true},"execution_count":22,"outputs":[{"name":"stdout","text":"Data saved to features_for_model.csv\n","output_type":"stream"}]},{"cell_type":"markdown","source":"# DO A TRAIN TEST SPLIT,OUTPUT CLASSES WILL BE TRY MULTIPLE TRADITIONAL ML ALGORITHMS WITH STRATIFIED X CROSS VALIDATION ","metadata":{}},{"cell_type":"markdown","source":"# 9 categories","metadata":{}},{"cell_type":"code","source":"# Step 1: Import Libraries\n# features_for_model AND labels\n# use the\n\nimport numpy as np\nimport pandas as pd\nfrom sklearn.model_selection import StratifiedKFold, cross_val_score, train_test_split\nfrom sklearn.metrics import accuracy_score, confusion_matrix, classification_report\nfrom sklearn.ensemble import RandomForestClassifier\nfrom sklearn.svm import SVC\nfrom sklearn.neighbors import KNeighborsClassifier\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.naive_bayes import GaussianNB\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\n# Step 2: Define the Models and Perform Stratified Cross-Validation\n# Define the models\nmodels = {\n    'Random Forest': RandomForestClassifier(),\n    'Support Vector Classifier': SVC(),\n    'K-Nearest Neighbors': KNeighborsClassifier(),\n    'Logistic Regression': LogisticRegression(max_iter=1000),\n    'Naive Bayes': GaussianNB()\n}\n\n# Prepare cross-validation and store accuracies\nkfold = StratifiedKFold(n_splits=10, shuffle=True, random_state=42)\nmodel_accuracies = {}\n\nfor name, model in models.items():\n    # Perform cross-validation\n    cv_accuracies = cross_val_score(model, features_for_model, labels, cv=kfold, scoring='accuracy')\n    model_accuracies[name] = cv_accuracies  # Store accuracy for each fold\n\n    # Print accuracy for each fold\n    print(f\"{name} - Cross-validation accuracies per fold: {cv_accuracies}\")\n    print(f\"{name} - Mean cross-validation accuracy: {np.mean(cv_accuracies):.4f}\\n\")\n\n    \n# Calculate mean and standard deviation of cross-validation accuracies\nmean_accuracies = {name: np.mean(acc) for name, acc in model_accuracies.items()}\nstd_accuracies = {name: np.std(acc) for name, acc in model_accuracies.items()}\n\n\n# Step 3: Plot the Mean Cross-Validation Accuracies\n# Plot the mean cross-validation accuracies with error bars for each model\nplt.figure(figsize=(10, 6))\nplt.bar(mean_accuracies.keys(), mean_accuracies.values(), yerr=std_accuracies.values(), capsize=5, color='skyblue')\nplt.xlabel('Machine Learning Algorithms')\nplt.ylabel('Mean Cross-Validation Accuracy')\nplt.title('Comparison of Mean Cross-Validation Accuracy Across Models')\nplt.ylim(0, 1)  # Set y-axis limit for better visibility\nplt.xticks(rotation=45)\nplt.show()\n\n# Step 4: Additional Performance Metrics \n\nfrom sklearn.metrics import precision_score, recall_score, f1_score\n\n# Train-test split for final evaluation\nX_train, X_test, y_train, y_test = train_test_split(features_for_model, labels, test_size=0.2, stratify=labels, random_state=42)\n\n# Initialize empty lists to store the metrics\nprecision_scores = []\nrecall_scores = []\nf1_scores = []\n\nfor name, model in models.items():\n    # Train and predict on the test split\n    model.fit(X_train, y_train)\n    y_pred = model.predict(X_test)\n    \n    # Calculate performance metrics\n    precision = precision_score(y_test, y_pred, average='weighted')\n    recall = recall_score(y_test, y_pred, average='weighted')\n    f1 = f1_score(y_test, y_pred, average='weighted')\n    \n    precision_scores.append(precision)\n    recall_scores.append(recall)\n    f1_scores.append(f1)\n\n# Plot metrics for each model\nmetrics_df = pd.DataFrame({\n    'Model': list(models.keys()),\n    'Precision': precision_scores,\n    'Recall': recall_scores,\n    'F1-Score': f1_scores\n}).set_index('Model')\n\nmetrics_df.plot(kind='bar', figsize=(10, 6))\nplt.title('Model Comparison - Precision, Recall, and F1 Score')\nplt.xlabel('Machine Learning Algorithms')\nplt.ylabel('Score')\nplt.ylim(0, 1)\nplt.xticks(rotation=45)\nplt.legend(loc=\"upper right\")\nplt.show()\n","metadata":{"execution":{"iopub.status.busy":"2024-11-15T04:28:56.042407Z","iopub.execute_input":"2024-11-15T04:28:56.043004Z","iopub.status.idle":"2024-11-15T04:31:58.790382Z","shell.execute_reply.started":"2024-11-15T04:28:56.042946Z","shell.execute_reply":"2024-11-15T04:31:58.789210Z"},"trusted":true},"execution_count":23,"outputs":[{"name":"stdout","text":"Random Forest - Cross-validation accuracies per fold: [0.26068376 0.31196581 0.23931624 0.29059829 0.28205128 0.27777778\n 0.27467811 0.29613734 0.30042918 0.22746781]\nRandom Forest - Mean cross-validation accuracy: 0.2761\n\nSupport Vector Classifier - Cross-validation accuracies per fold: [0.28632479 0.28632479 0.28632479 0.28632479 0.28632479 0.28632479\n 0.28755365 0.2832618  0.2832618  0.2832618 ]\nSupport Vector Classifier - Mean cross-validation accuracy: 0.2855\n\nK-Nearest Neighbors - Cross-validation accuracies per fold: [0.17521368 0.17094017 0.15384615 0.1965812  0.18803419 0.19230769\n 0.18454936 0.18025751 0.19313305 0.14592275]\nK-Nearest Neighbors - Mean cross-validation accuracy: 0.1781\n\n","output_type":"stream"},{"name":"stderr","text":"/opt/conda/lib/python3.10/site-packages/sklearn/linear_model/_logistic.py:458: ConvergenceWarning: lbfgs failed to converge (status=1):\nSTOP: TOTAL NO. of ITERATIONS REACHED LIMIT.\n\nIncrease the number of iterations (max_iter) or scale the data as shown in:\n    https://scikit-learn.org/stable/modules/preprocessing.html\nPlease also refer to the documentation for alternative solver options:\n    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression\n  n_iter_i = _check_optimize_result(\n/opt/conda/lib/python3.10/site-packages/sklearn/linear_model/_logistic.py:458: ConvergenceWarning: lbfgs failed to converge (status=1):\nSTOP: TOTAL NO. of ITERATIONS REACHED LIMIT.\n\nIncrease the number of iterations (max_iter) or scale the data as shown in:\n    https://scikit-learn.org/stable/modules/preprocessing.html\nPlease also refer to the documentation for alternative solver options:\n    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression\n  n_iter_i = _check_optimize_result(\n/opt/conda/lib/python3.10/site-packages/sklearn/linear_model/_logistic.py:458: ConvergenceWarning: lbfgs failed to converge (status=1):\nSTOP: TOTAL NO. of ITERATIONS REACHED LIMIT.\n\nIncrease the number of iterations (max_iter) or scale the data as shown in:\n    https://scikit-learn.org/stable/modules/preprocessing.html\nPlease also refer to the documentation for alternative solver options:\n    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression\n  n_iter_i = _check_optimize_result(\n/opt/conda/lib/python3.10/site-packages/sklearn/linear_model/_logistic.py:458: ConvergenceWarning: lbfgs failed to converge (status=1):\nSTOP: TOTAL NO. of ITERATIONS REACHED LIMIT.\n\nIncrease the number of iterations (max_iter) or scale the data as shown in:\n    https://scikit-learn.org/stable/modules/preprocessing.html\nPlease also refer to the documentation for alternative solver options:\n    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression\n  n_iter_i = _check_optimize_result(\n","output_type":"stream"},{"name":"stdout","text":"Logistic Regression - Cross-validation accuracies per fold: [0.27777778 0.27777778 0.26923077 0.28632479 0.29059829 0.28632479\n 0.28755365 0.28755365 0.28755365 0.28755365]\nLogistic Regression - Mean cross-validation accuracy: 0.2838\n\nNaive Bayes - Cross-validation accuracies per fold: [0.07264957 0.09401709 0.05128205 0.07264957 0.05555556 0.05982906\n 0.06437768 0.07296137 0.07296137 0.06437768]\nNaive Bayes - Mean cross-validation accuracy: 0.0681\n\n","output_type":"stream"},{"output_type":"display_data","data":{"text/plain":"<Figure size 1000x600 with 1 Axes>","image/png":"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Wh06dMi6vmH8SrwW1ViElClTau7cuapQoYI6d+4caQxKzZo1NWzYMK1atUqurq4KDQ3lbEECN3XqVM2ePVurVq3Sli1btH//fp0/f15dunTR6dOn9d577+nzzz/X48ePNW7cOGeXi1h28eJFNWzYUIUKFdLChQtVt25dTZw4UbVq1dL9+/dljNE777yjr776Shs3buQ4Ek101cNrK+LD5B9//KFt27bp5s2bqlKlit5//31duXJFZ86cUbly5azrdenSRVevXtWcOXPk4eHh7PIRz33wwQeqUKGCevTooUSJEmn06NF66623VKpUKd24cUMfffSRdu7cqQEDBqhfv36Snl53rG7duvL09NTixYsjjVdA/PNsF6vffvtNV65ckY+Pj3LmzKncuXMrKChI7733nq5fv65Vq1Ypb968kYJ0VGNbEL+tWrVKlStXVtKkSa1tXbp00fXr17VgwQJrt7sbN26oePHieueddzR79mxJ0q5du1SyZEn2mXiuTZs2Cg4OVqtWrVS7dm0NGzZMHTp0kCR9//33evDggb777jvr+nTVjCbnnewC/rulS5ea1KlTm5o1a5pWrVoZi8VievXqZR4/fmxd56+//jK9e/c23t7e5q+//nJitUgoevbsaTJmzGj9/cqVK6ZatWomVapU5s8//zTGGHPixAmTPn16U6VKFfPtt9+aqVOnmnfeeccUKFDA2mUiLCzMKfXj1evRo4dJly6dKViwoPH29jblypUz06ZNM8YY8+DBA1OuXDmTJ08ea7cbJFxDhgwxNWrUsOm+GxYWZurVq2fef/99a9ujR4+MMcbMnz/fZMyY0Vy4cMHmdji+xD8jR440gwcPNsYY4+/vb4oUKWISJ05sJk6caF3n/v37pmbNmuaLL75wVpmvNb5uwGvrxIkT6tatm4YMGaJVq1bpxx9/tA5qjJgl76+//tLIkSO1evVq/f777ypYsKAzS0YC8OTJE929e1dVq1aVJA0ePFjHjh3TsGHDVKVKFdWoUUO7d+/WG2+8IX9/f6VLl06LFi3SggULlCNHDu3fv9/aZYJvhBOG+fPna/bs2Vq+fLkOHDigLVu2KF++fJo4caLmzp2rpEmTas2aNbJYLNZLKSDh6t27t5YvXy6LxaIDBw7o3r17cnFx0SeffKItW7Zozpw5kmTtWWGMUdq0aeXl5WVzOxxf4pfH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"},"metadata":{}},{"name":"stderr","text":"/opt/conda/lib/python3.10/site-packages/sklearn/metrics/_classification.py:1344: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n  _warn_prf(average, modifier, msg_start, len(result))\n/opt/conda/lib/python3.10/site-packages/sklearn/linear_model/_logistic.py:458: ConvergenceWarning: lbfgs failed to converge (status=1):\nSTOP: TOTAL NO. of ITERATIONS REACHED LIMIT.\n\nIncrease the number of iterations (max_iter) or scale the data as shown in:\n    https://scikit-learn.org/stable/modules/preprocessing.html\nPlease also refer to the documentation for alternative solver options:\n    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression\n  n_iter_i = _check_optimize_result(\n/opt/conda/lib/python3.10/site-packages/sklearn/metrics/_classification.py:1344: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n  _warn_prf(average, modifier, msg_start, len(result))\n/opt/conda/lib/python3.10/site-packages/sklearn/metrics/_classification.py:1344: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n  _warn_prf(average, modifier, msg_start, len(result))\n","output_type":"stream"},{"output_type":"display_data","data":{"text/plain":"<Figure size 1000x600 with 1 Axes>","image/png":"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vHfvngMqAV6PiFsrTJo0SevWrXNwNXjbELwRXRhwGc/y9OdQbDtWCE6IsZycnPT3339rzJgxkqSFCxeqQYMGCggIcGxhwEt68g/NhAkT1LdvX6VMmTLW/cHBqwsPD7cG7zVr1mjfvn0EKbw2Tx9LFovFQZUgJnvyc2jbtm0KDQ2NdccKwQkxVmhoqCZNmqSZM2eqYcOGqlOnjqpVqyZvb29Hlwa8lIg/NAcOHNDly5c1evRo5c6d28FV4W1hjLEeQ926dVPr1q116tQp3b1717GF4Z3w5PE1depUdejQQSNGjNCJEyccXBlikiePk969e6tBgwZasGBBrDuBww1wEaM9fPhQderU0apVq1S7dm398ssvkqSwsDA5Ozs7uDrgxRhjdOjQIeXPn1+SNGXKFDVr1szBVeFtM3jwYI0fP14LFy5UwYIF5erq6uiS8JZ7sgWhe/fumjZtmnLlyqVbt27JYrFo0qRJKly4sIOrREzSu3dvTZkyRQsXLpSvr6+SJUvm6JKiFS1OiJEi8ryrq6sSJEigMmXK6J9//pGfn58kydnZWWFhYY4sEXiuiGPYGCOLxaK8efNq5syZkv7t4nDt2jVHloe3wJNncu/du6f169erT58++uijj3T9+nX99ttv+uqrrzRgwAAFBQU5sFK8rSJC06lTpxQYGKj169fL399fEyZM0Hvvvad69epp9+7dDq4SMcXff/+ttWvXasaMGSpevLicnZ31559/auDAgdq2bZsCAwMdXeIbF8fRBQBPi/iieeDAASVNmlSzZ89WQECAevfureXLl0v698xYRIvTzZs3lSRJEkeWDNh48izuo0ePFCdOHMWJE0cNGzbUw4cP1bp1a2XMmFHt27dXggQJHFssYqQnu8Vs2LBBqVOnlpOTky5duqSff/5ZS5cu1dWrV/X48WPt3LlT169f17hx42Ld9Qb47xYuXKguXbooWbJk6tu3rySpaNGicnNz03fffaf69evrxx9/pOUJevTokf766y/FiRNHe/bs0fTp07Vnzx7dunVL06ZN0+TJk1WhQgXr97h3ES1OiFEi3mxLly5VxYoVNW7cON26dUsJEiRQz549VaBAAa1YsUJDhgyRJPXp00dfffWVgoODHVw58K8nQ9P333+vL7/8UpUrV1aLFi304MEDtWrVShMnTlT//v01duxYrlNBJOHh4dYv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"},"metadata":{}}]},{"cell_type":"markdown","source":"# Create a new set of labels by using existing labels into 3 labels into Low medium and high arousal based ON letter that follows after \"A\" in labels\n\n# Used the new set of labels so that now there are 3 output classes and repeat steps below","metadata":{}},{"cell_type":"markdown","source":"\n\n\n","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"# Function to classify labels into 'Low', 'Medium', or 'High' arousal based on the character after \"A\"\ndef categorize_arousal_label(label):\n    arousal_level = label.split(\"A\")[1]  # Extract the character after \"A\"\n    \n    # Map the arousal level to Low, Medium, or High\n    if arousal_level in ['L']:\n        return 'Low'\n    elif arousal_level in ['M']:\n        return 'Medium'\n    else:\n        return 'High'\n\n# Apply the function to create new labels\nlabels = features_for_model['arousal_valence_label'].apply(categorize_arousal_label)\n\n\n\n","metadata":{"execution":{"iopub.status.busy":"2024-11-15T04:52:06.958389Z","iopub.execute_input":"2024-11-15T04:52:06.960168Z","iopub.status.idle":"2024-11-15T04:52:08.696035Z","shell.execute_reply.started":"2024-11-15T04:52:06.960117Z","shell.execute_reply":"2024-11-15T04:52:08.694516Z"},"trusted":true},"execution_count":24,"outputs":[{"traceback":["\u001b[0;31m---------------------------------------------------------------------------\u001b[0m","\u001b[0;31mKeyError\u001b[0m                                  Traceback (most recent call last)","File \u001b[0;32m/opt/conda/lib/python3.10/site-packages/pandas/core/indexes/base.py:3805\u001b[0m, in \u001b[0;36mIndex.get_loc\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m   3804\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 3805\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_engine\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_loc\u001b[49m\u001b[43m(\u001b[49m\u001b[43mcasted_key\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m   3806\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m err:\n","File \u001b[0;32mindex.pyx:167\u001b[0m, in \u001b[0;36mpandas._libs.index.IndexEngine.get_loc\u001b[0;34m()\u001b[0m\n","File \u001b[0;32mindex.pyx:196\u001b[0m, in \u001b[0;36mpandas._libs.index.IndexEngine.get_loc\u001b[0;34m()\u001b[0m\n","File \u001b[0;32mpandas/_libs/hashtable_class_helper.pxi:7081\u001b[0m, in \u001b[0;36mpandas._libs.hashtable.PyObjectHashTable.get_item\u001b[0;34m()\u001b[0m\n","File \u001b[0;32mpandas/_libs/hashtable_class_helper.pxi:7089\u001b[0m, in \u001b[0;36mpandas._libs.hashtable.PyObjectHashTable.get_item\u001b[0;34m()\u001b[0m\n","\u001b[0;31mKeyError\u001b[0m: 'arousal_valence_label'","\nThe above exception was the direct cause of the following exception:\n","\u001b[0;31mKeyError\u001b[0m                                  Traceback (most recent call last)","Cell \u001b[0;32mIn[24], line 14\u001b[0m\n\u001b[1;32m     11\u001b[0m         \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mHigh\u001b[39m\u001b[38;5;124m'\u001b[39m\n\u001b[1;32m     13\u001b[0m \u001b[38;5;66;03m# Apply the function to create new labels\u001b[39;00m\n\u001b[0;32m---> 14\u001b[0m labels \u001b[38;5;241m=\u001b[39m \u001b[43mfeatures_for_model\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43marousal_valence_label\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m]\u001b[49m\u001b[38;5;241m.\u001b[39mapply(categorize_arousal_label)\n","File \u001b[0;32m/opt/conda/lib/python3.10/site-packages/pandas/core/frame.py:4102\u001b[0m, in \u001b[0;36mDataFrame.__getitem__\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m   4100\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mcolumns\u001b[38;5;241m.\u001b[39mnlevels \u001b[38;5;241m>\u001b[39m \u001b[38;5;241m1\u001b[39m:\n\u001b[1;32m   4101\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_getitem_multilevel(key)\n\u001b[0;32m-> 4102\u001b[0m indexer \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mcolumns\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_loc\u001b[49m\u001b[43m(\u001b[49m\u001b[43mkey\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m   4103\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m is_integer(indexer):\n\u001b[1;32m   4104\u001b[0m     indexer \u001b[38;5;241m=\u001b[39m [indexer]\n","File \u001b[0;32m/opt/conda/lib/python3.10/site-packages/pandas/core/indexes/base.py:3812\u001b[0m, in \u001b[0;36mIndex.get_loc\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m   3807\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(casted_key, \u001b[38;5;28mslice\u001b[39m) \u001b[38;5;129;01mor\u001b[39;00m (\n\u001b[1;32m   3808\u001b[0m         \u001b[38;5;28misinstance\u001b[39m(casted_key, abc\u001b[38;5;241m.\u001b[39mIterable)\n\u001b[1;32m   3809\u001b[0m         \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28many\u001b[39m(\u001b[38;5;28misinstance\u001b[39m(x, \u001b[38;5;28mslice\u001b[39m) \u001b[38;5;28;01mfor\u001b[39;00m x \u001b[38;5;129;01min\u001b[39;00m casted_key)\n\u001b[1;32m   3810\u001b[0m     ):\n\u001b[1;32m   3811\u001b[0m         \u001b[38;5;28;01mraise\u001b[39;00m InvalidIndexError(key)\n\u001b[0;32m-> 3812\u001b[0m     \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m(key) \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01merr\u001b[39;00m\n\u001b[1;32m   3813\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m:\n\u001b[1;32m   3814\u001b[0m     \u001b[38;5;66;03m# If we have a listlike key, _check_indexing_error will raise\u001b[39;00m\n\u001b[1;32m   3815\u001b[0m     \u001b[38;5;66;03m#  InvalidIndexError. Otherwise we fall through and re-raise\u001b[39;00m\n\u001b[1;32m   3816\u001b[0m     \u001b[38;5;66;03m#  the TypeError.\u001b[39;00m\n\u001b[1;32m   3817\u001b[0m     \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_check_indexing_error(key)\n","\u001b[0;31mKeyError\u001b[0m: 'arousal_valence_label'"],"ename":"KeyError","evalue":"'arousal_valence_label'","output_type":"error"}]},{"cell_type":"code","source":"# gsr_features.shape","metadata":{"execution":{"iopub.status.busy":"2024-11-14T16:02:08.295598Z","iopub.execute_input":"2024-11-14T16:02:08.296015Z","iopub.status.idle":"2024-11-14T16:02:08.303425Z","shell.execute_reply.started":"2024-11-14T16:02:08.295978Z","shell.execute_reply":"2024-11-14T16:02:08.302369Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# IGNORE FROM HERE\n\n# Multi input LSTM with separate layer for PPG & GSR","metadata":{}},{"cell_type":"code","source":"# import torch\n# import torch.nn as nn\n# import torch.optim as optim\n# from torch.utils.data import DataLoader, random_split, TensorDataset\n# import matplotlib.pyplot as plt\n\n# # Check if GPU is available\n# device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n# print(f\"Using device: {device}\")\n\n# # Assume data_intervals is already populated from previous steps\n\n# # Split data into train and test sets (80% train, 20% test)\n# train_size = int(0.8 * len(data_intervals))\n# test_size = len(data_intervals) - train_size\n# train_data, test_data = random_split(data_intervals, [train_size, test_size])\n\n# # Convert data_intervals entries into tensors for PyTorch\n# def prepare_data(data):\n#     ppg_sequences = []\n#     gsr_sequences = []\n#     labels = []\n\n#     for item in data:\n#         ppg_sequences.append(torch.tensor(item['ppg_data'], dtype=torch.float32))\n#         gsr_sequences.append(torch.tensor(item['gsr_data'], dtype=torch.float32))\n#         labels.append(torch.tensor([item['arousal'], item['valence']], dtype=torch.float32))\n\n#     # Stack sequences and labels to create tensor batches\n#     ppg_sequences = nn.utils.rnn.pad_sequence(ppg_sequences, batch_first=True)\n#     gsr_sequences = nn.utils.rnn.pad_sequence(gsr_sequences, batch_first=True)\n#     labels = torch.stack(labels)\n\n#     return TensorDataset(ppg_sequences, gsr_sequences, labels)\n\n# train_dataset = prepare_data(train_data)\n# test_dataset = prepare_data(test_data)\n\n# # Load data into DataLoaders\n# batch_size = 32\n# train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\n# test_loader = DataLoader(test_dataset, batch_size=batch_size)\n\n# # Define the LSTM model with separate layers for PPG and GSR data\n# class MultiInputLSTM(nn.Module):\n#     def __init__(self, input_size_ppg, input_size_gsr, hidden_size, output_size, num_layers=1, dropout=0.3):\n#         super(MultiInputLSTM, self).__init__()\n#         # LSTM for PPG data\n#         self.lstm_ppg = nn.LSTM(input_size_ppg, hidden_size, num_layers, batch_first=True, dropout=dropout)\n        \n#         # LSTM for GSR data\n#         self.lstm_gsr = nn.LSTM(input_size_gsr, hidden_size, num_layers, batch_first=True, dropout=dropout)\n        \n#         # Fully connected layer to combine the outputs and predict arousal and valence\n#         self.fc = nn.Linear(hidden_size * 2, output_size)\n#         self.fc_dropout = nn.Dropout(dropout)  # Dropout after fully connected layer\n\n#     def forward(self, ppg_data, gsr_data):\n#         # PPG LSTM forward\n#         _, (h_ppg, _) = self.lstm_ppg(ppg_data)\n#         h_ppg = h_ppg[-1]  # Take the final hidden state\n\n#         # GSR LSTM forward\n#         _, (h_gsr, _) = self.lstm_gsr(gsr_data)\n#         h_gsr = h_gsr[-1]  # Take the final hidden state\n\n#         # Concatenate the hidden states from both LSTMs\n#         combined = torch.cat((h_ppg, h_gsr), dim=1)\n        \n#         # Apply dropout after fully connected layer\n#         output = self.fc(combined)\n#         output = self.fc_dropout(output)\n#         return output\n\n# # Model parameters\n# input_size_ppg = 1  # Assuming PPG data is a single feature per timestep\n# input_size_gsr = 1  # Assuming GSR data is a single feature per timestep\n# hidden_size = 128   # Changed hidden size\n# output_size = 2  # Arousal and Valence\n# num_layers = 2    # Changed number of layers\n# dropout = 0.3     # Dropout rate\n\n# # Instantiate the model, loss function, and optimizer with weight decay (L2 regularization)\n# model = MultiInputLSTM(input_size_ppg, input_size_gsr, hidden_size, output_size, num_layers, dropout)\n# model.to(device)  # Move model to GPU if available\n# criterion = nn.MSELoss()\n# optimizer = optim.Adam(model.parameters(), lr=0.0001, weight_decay=1e-5)  # Added weight decay\n\n# # Training function with validation evaluation\n# def train(model, train_loader, test_loader, criterion, optimizer, epochs=500):\n#     train_losses = []\n#     val_losses = []\n#     train_accuracies = []\n#     val_accuracies = []\n\n#     for epoch in range(epochs):\n#         model.train()\n#         total_train_loss = 0\n#         correct_train_predictions = 0\n#         total_train_predictions = 0\n\n#         for ppg_data, gsr_data, labels in train_loader:\n#             ppg_data = ppg_data.unsqueeze(-1).to(device)  # Move data to GPU\n#             gsr_data = gsr_data.unsqueeze(-1).to(device)  # Move data to GPU\n#             labels = labels.to(device)  # Move labels to GPU\n            \n#             # Zero the gradients\n#             optimizer.zero_grad()\n            \n#             # Forward pass\n#             outputs = model(ppg_data, gsr_data)\n#             loss = criterion(outputs, labels)\n#             total_train_loss += loss.item()\n            \n#             # Backward and optimize\n#             loss.backward()\n#             optimizer.step()\n            \n#             # Calculate training accuracy\n#             predictions = outputs.round()  # Rounding predictions to nearest integer\n#             correct_train_predictions += (predictions == labels).sum().item()\n#             total_train_predictions += labels.numel()\n\n#         # Calculate average training loss and accuracy\n#         avg_train_loss = total_train_loss / len(train_loader)\n#         train_losses.append(avg_train_loss)\n#         train_accuracy = (correct_train_predictions / total_train_predictions) * 100\n#         train_accuracies.append(train_accuracy)\n        \n#         # Evaluate on validation set\n#         val_loss, val_accuracy = evaluate(model, test_loader, criterion)\n#         val_losses.append(val_loss)\n#         val_accuracies.append(val_accuracy)\n\n#         print(f\"Epoch [{epoch+1}/{epochs}], \"\n#               f\"Train Loss: {avg_train_loss:.4f}, \"\n#               f\"Train Accuracy: {train_accuracy:.2f}%, \"\n#               f\"Validation Loss: {val_loss:.4f}, \"\n#               f\"Validation Accuracy: {val_accuracy:.2f}%\")\n\n#     return train_losses, val_losses, train_accuracies, val_accuracies\n\n# # Evaluation function\n# def evaluate(model, data_loader, criterion):\n#     model.eval()\n#     total_loss = 0\n#     correct_predictions = 0\n#     total_predictions = 0\n\n#     with torch.no_grad():\n#         for ppg_data, gsr_data, labels in data_loader:\n#             ppg_data = ppg_data.unsqueeze(-1).to(device)  # Move data to GPU\n#             gsr_data = gsr_data.unsqueeze(-1).to(device)  # Move data to GPU\n#             labels = labels.to(device)  # Move labels to GPU\n#             outputs = model(ppg_data, gsr_data)\n#             loss = criterion(outputs, labels)\n#             total_loss += loss.item()\n\n#             # Calculate accuracy based on mean absolute error tolerance\n#             predictions = outputs.round()  # Rounding predictions to nearest integer\n#             correct_predictions += (predictions == labels).sum().item()\n#             total_predictions += labels.numel()\n\n#     avg_loss = total_loss / len(data_loader)\n#     accuracy = (correct_predictions / total_predictions) * 100\n#     return avg_loss, accuracy\n\n# # Train the model and retrieve metrics\n# epochs = 500  # Changed epochs to 500\n# train_losses, val_losses, train_accuracies, val_accuracies = train(model, train_loader, test_loader, criterion, optimizer, epochs=epochs)\n\n# # Plotting training and validation loss\n# plt.figure(figsize=(12, 6))\n# plt.subplot(1, 2, 1)\n# plt.plot(range(1, epochs + 1), train_losses, label='Training Loss')\n# plt.plot(range(1, epochs + 1), val_losses, label='Validation Loss')\n# plt.xlabel('Epochs')\n# plt.ylabel('Loss')\n# plt.legend()\n# plt.title('Training and Validation Loss')\n\n# # Plotting training and validation accuracy\n# plt.subplot(1, 2, 2)\n# plt.plot(range(1, epochs + 1), train_accuracies, label='Training Accuracy')\n# plt.plot(range(1, epochs + 1), val_accuracies, label='Validation Accuracy')\n# plt.xlabel('Epochs')\n# plt.ylabel('Accuracy (%)')\n# plt.legend()\n# plt.title('Training and Validation Accuracy')\n\n# plt.show()\n","metadata":{"execution":{"iopub.status.busy":"2024-11-13T19:02:41.732485Z","iopub.execute_input":"2024-11-13T19:02:41.732924Z","iopub.status.idle":"2024-11-13T19:09:55.918421Z","shell.execute_reply.started":"2024-11-13T19:02:41.732886Z","shell.execute_reply":"2024-11-13T19:09:55.91716Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Categorical Ouptuts without label encoding","metadata":{}},{"cell_type":"code","source":"# import torch\n# import torch.nn as nn\n# import torch.optim as optim\n# from torch.utils.data import DataLoader, random_split, TensorDataset\n# import matplotlib.pyplot as plt\n\n# # Assume data_intervals is already populated from previous steps\n\n# # Split data into train and test sets (80% train, 20% test)\n# train_size = int(0.8 * len(data_intervals))\n# test_size = len(data_intervals) - train_size\n# train_data, test_data = random_split(data_intervals, [train_size, test_size])\n\n# # Convert data_intervals entries into tensors for PyTorch\n# def prepare_data(data):\n#     ppg_sequences = []\n#     gsr_sequences = []\n#     labels = []\n\n#     for item in data:\n#         ppg_sequences.append(torch.tensor(item['ppg_data'], dtype=torch.float32))\n#         gsr_sequences.append(torch.tensor(item['gsr_data'], dtype=torch.float32))\n#         # Convert arousal and valence to integer class indices (1-9 for both, adjusting to 0-8)\n#         arousal_class = int(item['arousal']) - 1  # Adjusting arousal to range 0-8\n#         valence_class = int(item['valence']) - 1  # Adjusting valence to range 0-8\n#         # Combine the classes into a single value (arousal class * 9 + valence class)\n#         combined_class = arousal_class * 9 + valence_class\n#         labels.append(torch.tensor(combined_class, dtype=torch.long))  # Long tensor for classification\n\n#     # Stack sequences and labels to create tensor batches\n#     ppg_sequences = nn.utils.rnn.pad_sequence(ppg_sequences, batch_first=True)\n#     gsr_sequences = nn.utils.rnn.pad_sequence(gsr_sequences, batch_first=True)\n#     labels = torch.stack(labels)\n\n#     return TensorDataset(ppg_sequences, gsr_sequences, labels)\n\n# train_dataset = prepare_data(train_data)\n# test_dataset = prepare_data(test_data)\n\n# # Load data into DataLoaders\n# batch_size = 32\n# train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\n# test_loader = DataLoader(test_dataset, batch_size=batch_size)\n\n# # Define the LSTM model with separate layers for PPG and GSR data\n# class MultiInputLSTM(nn.Module):\n#     def __init__(self, input_size_ppg, input_size_gsr, hidden_size, output_size, num_layers=1):\n#         super(MultiInputLSTM, self).__init__()\n#         # LSTM for PPG data\n#         self.lstm_ppg = nn.LSTM(input_size_ppg, hidden_size, num_layers, batch_first=True)\n        \n#         # LSTM for GSR data\n#         self.lstm_gsr = nn.LSTM(input_size_gsr, hidden_size, num_layers, batch_first=True)\n        \n#         # Fully connected layer to combine the outputs and predict arousal and valence\n#         self.fc = nn.Linear(hidden_size * 2, output_size)\n\n#     def forward(self, ppg_data, gsr_data):\n#         # PPG LSTM forward\n#         _, (h_ppg, _) = self.lstm_ppg(ppg_data)\n#         h_ppg = h_ppg[-1]  # Take the final hidden state\n\n#         # GSR LSTM forward\n#         _, (h_gsr, _) = self.lstm_gsr(gsr_data)\n#         h_gsr = h_gsr[-1]  # Take the final hidden state\n\n#         # Concatenate the hidden states from both LSTMs\n#         combined = torch.cat((h_ppg, h_gsr), dim=1)\n        \n#         # Fully connected output layer with softmax for categorical prediction\n#         output = self.fc(combined)\n#         return output\n\n# # Model parameters\n# input_size_ppg = 1  # Assuming PPG data is a single feature per timestep\n# input_size_gsr = 1  # Assuming GSR data is a single feature per timestep\n# hidden_size = 128   # Updated hidden size\n# output_size = 81    # 81 classes for combined arousal (0-8) and valence (0-8)\n# num_layers = 1\n\n# # Instantiate the model, loss function, and optimizer\n# model = MultiInputLSTM(input_size_ppg, input_size_gsr, hidden_size, output_size, num_layers)\n# criterion = nn.CrossEntropyLoss()  # CrossEntropyLoss for classification\n# optimizer = optim.Adam(model.parameters(), lr=0.0001)\n\n# # Training function with validation evaluation\n# def train(model, train_loader, test_loader, criterion, optimizer, epochs=500):\n#     train_losses = []\n#     val_losses = []\n#     train_accuracies = []\n#     val_accuracies = []\n\n#     for epoch in range(epochs):\n#         model.train()\n#         total_train_loss = 0\n#         correct_train_predictions = 0\n#         total_train_predictions = 0\n\n#         for ppg_data, gsr_data, labels in train_loader:\n#             ppg_data = ppg_data.unsqueeze(-1)\n#             gsr_data = gsr_data.unsqueeze(-1)\n            \n#             # Zero the gradients\n#             optimizer.zero_grad()\n            \n#             # Forward pass\n#             outputs = model(ppg_data, gsr_data)\n#             loss = criterion(outputs, labels)  # Directly use labels without reshaping\n#             total_train_loss += loss.item()\n            \n#             # Backward and optimize\n#             loss.backward()\n#             optimizer.step()\n            \n#             # Calculate training accuracy\n#             _, predicted = torch.max(outputs, 1)  # Get the predicted class index\n#             correct_train_predictions += (predicted == labels).sum().item()\n#             total_train_predictions += labels.numel()\n        \n#         # Calculate average training loss and accuracy\n#         avg_train_loss = total_train_loss / len(train_loader)\n#         train_losses.append(avg_train_loss)\n#         train_accuracy = (correct_train_predictions / total_train_predictions) * 100\n#         train_accuracies.append(train_accuracy)\n        \n#         # Evaluate on validation set\n#         val_loss, val_accuracy = evaluate(model, test_loader, criterion)\n#         val_losses.append(val_loss)\n#         val_accuracies.append(val_accuracy)\n\n#         print(f\"Epoch [{epoch+1}/{epochs}], \"\n#               f\"Train Loss: {avg_train_loss:.4f}, \"\n#               f\"Train Accuracy: {train_accuracy:.2f}%, \"\n#               f\"Validation Loss: {val_loss:.4f}, \"\n#               f\"Validation Accuracy: {val_accuracy:.2f}%\")\n\n#     return train_losses, val_losses, train_accuracies, val_accuracies\n\n# # Evaluation function\n# def evaluate(model, data_loader, criterion):\n#     model.eval()\n#     total_loss = 0\n#     correct_predictions = 0\n#     total_predictions = 0\n\n#     with torch.no_grad():\n#         for ppg_data, gsr_data, labels in data_loader:\n#             ppg_data = ppg_data.unsqueeze(-1)\n#             gsr_data = gsr_data.unsqueeze(-1)\n#             outputs = model(ppg_data, gsr_data)\n#             loss = criterion(outputs, labels)\n#             total_loss += loss.item()\n\n#             # Calculate accuracy\n#             _, predicted = torch.max(outputs, 1)\n#             correct_predictions += (predicted == labels).sum().item()\n#             total_predictions += labels.numel()\n\n#     avg_loss = total_loss / len(data_loader)\n#     accuracy = (correct_predictions / total_predictions) * 100\n#     return avg_loss, accuracy\n\n# # Train the model and retrieve metrics\n# epochs = 500  # Updated epochs to 500\n# train_losses, val_losses, train_accuracies, val_accuracies = train(model, train_loader, test_loader, criterion, optimizer, epochs=epochs)\n\n# # Plotting training and validation loss\n# plt.figure(figsize=(12, 6))\n# plt.subplot(1, 2, 1)\n# plt.plot(range(1, epochs + 1), train_losses, label='Training Loss')\n# plt.plot(range(1, epochs + 1), val_losses, label='Validation Loss')\n# plt.xlabel('Epochs')\n# plt.ylabel('Loss')\n# plt.legend()\n# plt.title('Training and Validation Loss')\n\n# # Plotting training and validation accuracy\n# plt.subplot(1, 2, 2)\n# plt.plot(range(1, epochs + 1), train_accuracies, label='Training Accuracy')\n# plt.plot(range(1, epochs + 1), val_accuracies, label='Validation Accuracy')\n# plt.xlabel('Epochs')\n# plt.ylabel('Accuracy (%)')\n# plt.legend()\n# plt.title('Training and Validation Accuracy')\n\n# plt.show()\n","metadata":{"execution":{"iopub.status.busy":"2024-11-13T19:27:46.018357Z","iopub.execute_input":"2024-11-13T19:27:46.019327Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Categorical Outputs with label encoding low medium high A & V","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import DataLoader, random_split, TensorDataset\nimport matplotlib.pyplot as plt\nfrom sklearn.preprocessing import LabelEncoder\nimport random\nimport numpy as np\n\n# Set seed for reproducibility\nseed = 42\ntorch.manual_seed(seed)\nnp.random.seed(seed)\nrandom.seed(seed)\nif torch.cuda.is_available():\n    torch.cuda.manual_seed_all(seed)\n\n# Check if GPU is available and set device\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {device}\")\n\n# Assume data_intervals is already populated from previous steps\n\n# Initialize the LabelEncoder for 9 unique arousal-valence combinations\nlabel_encoder = LabelEncoder()\narousal_valence_labels = [item['arousal_valence_label'] for item in data_intervals]\nlabel_encoder.fit(arousal_valence_labels)\n\n# Split data into train and test sets (80% train, 20% test)\ntrain_size = int(0.8 * len(data_intervals))\ntest_size = len(data_intervals) - train_size\ntrain_data, test_data = random_split(data_intervals, [train_size, test_size])\n\ndef prepare_data(data):\n    ppg_sequences = []\n    gsr_sequences = []\n    labels = []\n\n    for item in data:\n        ppg_sequences.append(torch.tensor(item['ppg_data'], dtype=torch.float32))\n        gsr_sequences.append(torch.tensor(item['gsr_data'], dtype=torch.float32))\n        combined_class = label_encoder.transform([item['arousal_valence_label']])[0]\n        labels.append(torch.tensor(combined_class, dtype=torch.long))\n\n    ppg_sequences = nn.utils.rnn.pad_sequence(ppg_sequences, batch_first=True)\n    gsr_sequences = nn.utils.rnn.pad_sequence(gsr_sequences, batch_first=True)\n    labels = torch.stack(labels)\n\n    return TensorDataset(ppg_sequences, gsr_sequences, labels)\n\ntrain_dataset = prepare_data(train_data)\ntest_dataset = prepare_data(test_data)\n\nbatch_size = 32\ntrain_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\ntest_loader = DataLoader(test_dataset, batch_size=batch_size)\n\nclass MultiInputLSTM(nn.Module):\n    def __init__(self, input_size_ppg, input_size_gsr, hidden_size, output_size, num_layers=1):\n        super(MultiInputLSTM, self).__init__()\n        self.lstm_ppg = nn.LSTM(input_size_ppg, hidden_size, num_layers, batch_first=True)\n        self.lstm_gsr = nn.LSTM(input_size_gsr, hidden_size, num_layers, batch_first=True)\n        self.fc = nn.Linear(hidden_size * 2, output_size)\n\n    def forward(self, ppg_data, gsr_data):\n        _, (h_ppg, _) = self.lstm_ppg(ppg_data)\n        h_ppg = h_ppg[-1]\n        _, (h_gsr, _) = self.lstm_gsr(gsr_data)\n        h_gsr = h_gsr[-1]\n        combined = torch.cat((h_ppg, h_gsr), dim=1)\n        output = self.fc(combined)\n        return output\n\ninput_size_ppg = 1\ninput_size_gsr = 1\nhidden_size = 128\noutput_size = 9\nnum_layers = 1\n\n# Instantiate the model, loss function, and optimizer\nmodel = MultiInputLSTM(input_size_ppg, input_size_gsr, hidden_size, output_size, num_layers).to(device)\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(model.parameters(), lr=0.0001)\n\n# Function to train the model with saving best model state\ndef train(model, train_loader, test_loader, criterion, optimizer, epochs=500):\n    train_losses = []\n    val_losses = []\n    train_accuracies = []\n    val_accuracies = []\n    best_val_accuracy = 0.0  # Track the best validation accuracy\n    best_model_state = None  # To store the best model state\n\n    for epoch in range(epochs):\n        model.train()\n        total_train_loss = 0\n        correct_train_predictions = 0\n        total_train_predictions = 0\n\n        for ppg_data, gsr_data, labels in train_loader:\n            ppg_data = ppg_data.unsqueeze(-1).to(device)\n            gsr_data = gsr_data.unsqueeze(-1).to(device)\n            labels = labels.to(device)\n\n            optimizer.zero_grad()\n            outputs = model(ppg_data, gsr_data)\n            loss = criterion(outputs, labels)\n            total_train_loss += loss.item()\n\n            loss.backward()\n            optimizer.step()\n\n            _, predicted = torch.max(outputs, 1)\n            correct_train_predictions += (predicted == labels).sum().item()\n            total_train_predictions += labels.numel()\n\n        avg_train_loss = total_train_loss / len(train_loader)\n        train_losses.append(avg_train_loss)\n        train_accuracy = (correct_train_predictions / total_train_predictions) * 100\n        train_accuracies.append(train_accuracy)\n\n        val_loss, val_accuracy = evaluate(model, test_loader, criterion)\n        val_losses.append(val_loss)\n        val_accuracies.append(val_accuracy)\n\n        # Check if current model is the best, and save if it is\n        if val_accuracy > best_val_accuracy:\n            best_val_accuracy = val_accuracy\n            best_model_state = model.state_dict()  # Save the model state\n\n        print(f\"Epoch [{epoch+1}/{epochs}], \"\n              f\"Train Loss: {avg_train_loss:.4f}, \"\n              f\"Train Accuracy: {train_accuracy:.2f}%, \"\n              f\"Validation Loss: {val_loss:.4f}, \"\n              f\"Validation Accuracy: {val_accuracy:.2f}%\")\n\n    # Save the best model state to a file\n    torch.save(best_model_state, \"best_model.pth\")\n    print(f\"Best model saved with validation accuracy: {best_val_accuracy:.2f}%\")\n\n    return train_losses, val_losses, train_accuracies, val_accuracies\n\ndef evaluate(model, data_loader, criterion):\n    model.eval()\n    total_loss = 0\n    correct_predictions = 0\n    total_predictions = 0\n\n    with torch.no_grad():\n        for ppg_data, gsr_data, labels in data_loader:\n            ppg_data = ppg_data.unsqueeze(-1).to(device)\n            gsr_data = gsr_data.unsqueeze(-1).to(device)\n            labels = labels.to(device)\n\n            outputs = model(ppg_data, gsr_data)\n            loss = criterion(outputs, labels)\n            total_loss += loss.item()\n\n            _, predicted = torch.max(outputs, 1)\n            correct_predictions += (predicted == labels).sum().item()\n            total_predictions += labels.numel()\n\n    avg_loss = total_loss / len(data_loader)\n    accuracy = (correct_predictions / total_predictions) * 100\n    return avg_loss, accuracy\n\nepochs = 500\ntrain_losses, val_losses, train_accuracies, val_accuracies = train(model, train_loader, test_loader, criterion, optimizer, epochs=epochs)\n\nplt.figure(figsize=(12, 6))\nplt.subplot(1, 2, 1)\nplt.plot(range(1, epochs + 1), train_losses, label='Training Loss')\nplt.plot(range(1, epochs + 1), val_losses, label='Validation Loss')\nplt.xlabel('Epochs')\nplt.ylabel('Loss')\nplt.legend()\nplt.title('Training and Validation Loss')\n\nplt.subplot(1, 2, 2)\nplt.plot(range(1, epochs + 1), train_accuracies, label='Training Accuracy')\nplt.plot(range(1, epochs + 1), val_accuracies, label='Validation Accuracy')\nplt.xlabel('Epochs')\nplt.ylabel('Accuracy (%)')\nplt.legend()\nplt.title('Training and Validation Accuracy')\n\nplt.show()\n","metadata":{"execution":{"iopub.status.busy":"2024-11-14T03:55:49.516294Z","iopub.execute_input":"2024-11-14T03:55:49.516687Z","iopub.status.idle":"2024-11-14T04:00:16.972822Z","shell.execute_reply.started":"2024-11-14T03:55:49.516642Z","shell.execute_reply":"2024-11-14T04:00:16.971517Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Regression values as outputs","metadata":{}},{"cell_type":"code","source":"# import torch\n# import torch.nn as nn\n# import torch.optim as optim\n# from torch.utils.data import DataLoader, random_split, TensorDataset\n# import matplotlib.pyplot as plt\n\n# # Assume data_intervals is already populated from previous steps\n\n# # Check if GPU is available and use it\n# device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# # Split data into train and test sets (80% train, 20% test)\n# train_size = int(0.8 * len(data_intervals))\n# test_size = len(data_intervals) - train_size\n# train_data, test_data = random_split(data_intervals, [train_size, test_size])\n\n# # Convert data_intervals entries into tensors for PyTorch\n# def prepare_data(data):\n#     ppg_sequences = []\n#     gsr_sequences = []\n#     labels = []\n\n#     for item in data:\n#         ppg_sequences.append(torch.tensor(item['ppg_data'], dtype=torch.float32))\n#         gsr_sequences.append(torch.tensor(item['gsr_data'], dtype=torch.float32))\n#         labels.append(torch.tensor([item['arousal'], item['valence']], dtype=torch.float32))\n\n#     # Stack sequences and labels to create tensor batches\n#     ppg_sequences = nn.utils.rnn.pad_sequence(ppg_sequences, batch_first=True)\n#     gsr_sequences = nn.utils.rnn.pad_sequence(gsr_sequences, batch_first=True)\n#     labels = torch.stack(labels)\n\n#     return TensorDataset(ppg_sequences, gsr_sequences, labels)\n\n# train_dataset = prepare_data(train_data)\n# test_dataset = prepare_data(test_data)\n\n# # Load data into DataLoaders\n# batch_size = 32\n# train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\n# test_loader = DataLoader(test_dataset, batch_size=batch_size)\n\n# # Define the LSTM model with separate layers for PPG and GSR data\n# class MultiInputLSTM(nn.Module):\n#     def __init__(self, input_size_ppg, input_size_gsr, hidden_size, output_size, num_layers=1):\n#         super(MultiInputLSTM, self).__init__()\n#         # LSTM for PPG data\n#         self.lstm_ppg = nn.LSTM(input_size_ppg, hidden_size, num_layers, batch_first=True)\n        \n#         # LSTM for GSR data\n#         self.lstm_gsr = nn.LSTM(input_size_gsr, hidden_size, num_layers, batch_first=True)\n        \n#         # Fully connected layer to combine the outputs and predict arousal and valence\n#         self.fc = nn.Linear(hidden_size * 2, output_size)\n\n#     def forward(self, ppg_data, gsr_data):\n#         # PPG LSTM forward\n#         _, (h_ppg, _) = self.lstm_ppg(ppg_data)\n#         h_ppg = h_ppg[-1]  # Take the final hidden state\n\n#         # GSR LSTM forward\n#         _, (h_gsr, _) = self.lstm_gsr(gsr_data)\n#         h_gsr = h_gsr[-1]  # Take the final hidden state\n\n#         # Concatenate the hidden states from both LSTMs\n#         combined = torch.cat((h_ppg, h_gsr), dim=1)\n        \n#         # Fully connected output layer\n#         output = self.fc(combined)\n#         return output\n\n# # Model parameters\n# input_size_ppg = 1  # Assuming PPG data is a single feature per timestep\n# input_size_gsr = 1  # Assuming GSR data is a single feature per timestep\n# hidden_size = 128   # Updated hidden size\n# output_size = 2     # Arousal and Valence\n# num_layers = 1\n\n# # Instantiate the model, loss function, and optimizer\n# model = MultiInputLSTM(input_size_ppg, input_size_gsr, hidden_size, output_size, num_layers)\n# model = model.to(device)  # Move model to GPU if available\n# criterion = nn.MSELoss()\n# optimizer = optim.Adam(model.parameters(), lr=0.0001)\n\n# # Training function with validation evaluation\n# def train(model, train_loader, test_loader, criterion, optimizer, epochs=500):\n#     train_losses = []\n#     val_losses = []\n#     train_accuracies = []\n#     val_accuracies = []\n\n#     for epoch in range(epochs):\n#         model.train()\n#         total_train_loss = 0\n#         correct_train_predictions = 0\n#         total_train_predictions = 0\n\n#         for ppg_data, gsr_data, labels in train_loader:\n#             ppg_data = ppg_data.unsqueeze(-1).to(device)  # Move data to GPU\n#             gsr_data = gsr_data.unsqueeze(-1).to(device)  # Move data to GPU\n#             labels = labels.to(device)  # Move labels to GPU\n            \n#             # Zero the gradients\n#             optimizer.zero_grad()\n            \n#             # Forward pass\n#             outputs = model(ppg_data, gsr_data)\n#             loss = criterion(outputs, labels)\n#             total_train_loss += loss.item()\n            \n#             # Backward and optimize\n#             loss.backward()\n#             optimizer.step()\n            \n#             # Calculate training accuracy\n#             predictions = outputs.round()  # Rounding predictions to nearest integer\n#             correct_train_predictions += (predictions == labels).sum().item()\n#             total_train_predictions += labels.numel()\n        \n#         # Calculate average training loss and accuracy\n#         avg_train_loss = total_train_loss / len(train_loader)\n#         train_losses.append(avg_train_loss)\n#         train_accuracy = (correct_train_predictions / total_train_predictions) * 100\n#         train_accuracies.append(train_accuracy)\n        \n#         # Evaluate on validation set\n#         val_loss, val_accuracy = evaluate(model, test_loader, criterion)\n#         val_losses.append(val_loss)\n#         val_accuracies.append(val_accuracy)\n\n#         print(f\"Epoch [{epoch+1}/{epochs}], \"\n#               f\"Train Loss: {avg_train_loss:.4f}, \"\n#               f\"Train Accuracy: {train_accuracy:.2f}%, \"\n#               f\"Validation Loss: {val_loss:.4f}, \"\n#               f\"Validation Accuracy: {val_accuracy:.2f}%\")\n\n#     return train_losses, val_losses, train_accuracies, val_accuracies\n\n# # Evaluation function\n# def evaluate(model, data_loader, criterion):\n#     model.eval()\n#     total_loss = 0\n#     correct_predictions = 0\n#     total_predictions = 0\n\n#     with torch.no_grad():\n#         for ppg_data, gsr_data, labels in data_loader:\n#             ppg_data = ppg_data.unsqueeze(-1).to(device)  # Move data to GPU\n#             gsr_data = gsr_data.unsqueeze(-1).to(device)  # Move data to GPU\n#             labels = labels.to(device)  # Move labels to GPU\n#             outputs = model(ppg_data, gsr_data)\n#             loss = criterion(outputs, labels)\n#             total_loss += loss.item()\n\n#             # Calculate accuracy based on mean absolute error tolerance\n#             predictions = outputs.round()  # Rounding predictions to nearest integer\n#             correct_predictions += (predictions == labels).sum().item()\n#             total_predictions += labels.numel()\n\n#     avg_loss = total_loss / len(data_loader)\n#     accuracy = (correct_predictions / total_predictions) * 100\n#     return avg_loss, accuracy\n\n# # Train the model and retrieve metrics\n# epochs = 500  # Updated epochs to 500\n# train_losses, val_losses, train_accuracies, val_accuracies = train(model, train_loader, test_loader, criterion, optimizer, epochs=epochs)\n\n# # Plotting training and validation loss\n# plt.figure(figsize=(12, 6))\n# plt.subplot(1, 2, 1)\n# plt.plot(range(1, epochs + 1), train_losses, label='Training Loss')\n# plt.plot(range(1, epochs + 1), val_losses, label='Validation Loss')\n# plt.xlabel('Epochs')\n# plt.ylabel('Loss')\n# plt.legend()\n# plt.title('Training and Validation Loss')\n\n# # Plotting training and validation accuracy\n# plt.subplot(1, 2, 2)\n# plt.plot(range(1, epochs + 1), train_accuracies, label='Training Accuracy')\n# plt.plot(range(1, epochs + 1), val_accuracies, label='Validation Accuracy')\n# plt.xlabel('Epochs')\n# plt.ylabel('Accuracy (%)')\n# plt.legend()\n# plt.title('Training and Validation Accuracy')\n\n# plt.show()\n","metadata":{"execution":{"iopub.status.busy":"2024-11-13T19:15:37.52818Z","iopub.execute_input":"2024-11-13T19:15:37.528672Z","iopub.status.idle":"2024-11-13T19:20:12.779908Z","shell.execute_reply.started":"2024-11-13T19:15:37.528631Z","shell.execute_reply":"2024-11-13T19:20:12.778575Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"# import torch\n# import torch.nn as nn\n# import torch.optim as optim\n# from torch.utils.data import DataLoader, random_split, TensorDataset\n# import matplotlib.pyplot as plt\n\n# # Assume data_intervals is already populated from previous steps\n\n# # Split data into train and test sets (80% train, 20% test)\n# train_size = int(0.8 * len(data_intervals))\n# test_size = len(data_intervals) - train_size\n# train_data, test_data = random_split(data_intervals, [train_size, test_size])\n\n# # Convert data_intervals entries into tensors for PyTorch\n# def prepare_data(data):\n#     ppg_sequences = []\n#     gsr_sequences = []\n#     labels = []\n\n#     for item in data:\n#         ppg_sequences.append(torch.tensor(item['ppg_data'], dtype=torch.float32))\n#         gsr_sequences.append(torch.tensor(item['gsr_data'], dtype=torch.float32))\n#         labels.append(torch.tensor([item['arousal'], item['valence']], dtype=torch.float32))\n\n#     # Stack sequences and labels to create tensor batches\n#     ppg_sequences = nn.utils.rnn.pad_sequence(ppg_sequences, batch_first=True)\n#     gsr_sequences = nn.utils.rnn.pad_sequence(gsr_sequences, batch_first=True)\n#     labels = torch.stack(labels)\n\n#     return TensorDataset(ppg_sequences, gsr_sequences, labels)\n\n# train_dataset = prepare_data(train_data)\n# test_dataset = prepare_data(test_data)\n\n# # Load data into DataLoaders\n# batch_size = 32\n# train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\n# test_loader = DataLoader(test_dataset, batch_size=batch_size)\n\n# # Define the LSTM model with separate layers for PPG and GSR data\n# class MultiInputLSTM(nn.Module):\n#     def __init__(self, input_size_ppg, input_size_gsr, hidden_size, output_size, num_layers=1):\n#         super(MultiInputLSTM, self).__init__()\n#         # LSTM for PPG data\n#         self.lstm_ppg = nn.LSTM(input_size_ppg, hidden_size, num_layers, batch_first=True)\n        \n#         # LSTM for GSR data\n#         self.lstm_gsr = nn.LSTM(input_size_gsr, hidden_size, num_layers, batch_first=True)\n        \n#         # Fully connected layer to combine the outputs and predict arousal and valence\n#         self.fc = nn.Linear(hidden_size * 2, output_size)\n\n#     def forward(self, ppg_data, gsr_data):\n#         # PPG LSTM forward\n#         _, (h_ppg, _) = self.lstm_ppg(ppg_data)\n#         h_ppg = h_ppg[-1]  # Take the final hidden state\n\n#         # GSR LSTM forward\n#         _, (h_gsr, _) = self.lstm_gsr(gsr_data)\n#         h_gsr = h_gsr[-1]  # Take the final hidden state\n\n#         # Concatenate the hidden states from both LSTMs\n#         combined = torch.cat((h_ppg, h_gsr), dim=1)\n        \n#         # Fully connected output layer\n#         output = self.fc(combined)\n#         return output\n\n# # Model parameters\n# input_size_ppg = 1  # Assuming PPG data is a single feature per timestep\n# input_size_gsr = 1  # Assuming GSR data is a single feature per timestep\n# hidden_size = 64\n# output_size = 2  # Arousal and Valence\n# num_layers = 1\n\n# # Instantiate the model, loss function, and optimizer\n# model = MultiInputLSTM(input_size_ppg, input_size_gsr, hidden_size, output_size, num_layers)\n# criterion = nn.MSELoss()\n# optimizer = optim.Adam(model.parameters(), lr=0.0001)\n\n# # Training function with validation evaluation\n# def train(model, train_loader, test_loader, criterion, optimizer, epochs=50):\n#     train_losses = []\n#     val_losses = []\n#     train_accuracies = []\n#     val_accuracies = []\n\n#     for epoch in range(epochs):\n#         model.train()\n#         total_train_loss = 0\n\n#         for ppg_data, gsr_data, labels in train_loader:\n#             ppg_data = ppg_data.unsqueeze(-1)\n#             gsr_data = gsr_data.unsqueeze(-1)\n            \n#             # Zero the gradients\n#             optimizer.zero_grad()\n            \n#             # Forward pass\n#             outputs = model(ppg_data, gsr_data)\n#             loss = criterion(outputs, labels)\n#             total_train_loss += loss.item()\n            \n#             # Backward and optimize\n#             loss.backward()\n#             optimizer.step()\n        \n#         # Calculate average training loss\n#         avg_train_loss = total_train_loss / len(train_loader)\n#         train_losses.append(avg_train_loss)\n        \n#         # Evaluate on validation set\n#         val_loss, val_accuracy = evaluate(model, test_loader, criterion)\n#         val_losses.append(val_loss)\n#         val_accuracies.append(val_accuracy)\n\n#         print(f\"Epoch [{epoch+1}/{epochs}], \"\n#               f\"Train Loss: {avg_train_loss:.4f}, \"\n#               f\"Validation Loss: {val_loss:.4f}, \"\n#               f\"Validation Accuracy: {val_accuracy:.2f}%\")\n\n#     return train_losses, val_losses, val_accuracies\n\n# # Evaluation function\n# def evaluate(model, data_loader, criterion):\n#     model.eval()\n#     total_loss = 0\n#     correct_predictions = 0\n#     total_predictions = 0\n\n#     with torch.no_grad():\n#         for ppg_data, gsr_data, labels in data_loader:\n#             ppg_data = ppg_data.unsqueeze(-1)\n#             gsr_data = gsr_data.unsqueeze(-1)\n#             outputs = model(ppg_data, gsr_data)\n#             loss = criterion(outputs, labels)\n#             total_loss += loss.item()\n\n#             # Calculate accuracy based on mean absolute error tolerance\n#             predictions = outputs.round()  # Rounding predictions to nearest integer\n#             correct_predictions += (predictions == labels).sum().item()\n#             total_predictions += labels.numel()\n\n#     avg_loss = total_loss / len(data_loader)\n#     accuracy = (correct_predictions / total_predictions) * 100\n#     return avg_loss, accuracy\n\n# # Train the model and retrieve metrics\n# epochs = 200\n# train_losses, val_losses, val_accuracies = train(model, train_loader, test_loader, criterion, optimizer, epochs=epochs)\n\n# # Plotting training and validation loss\n# plt.figure(figsize=(12, 6))\n# plt.subplot(1, 2, 1)\n# plt.plot(range(1, epochs + 1), train_losses, label='Training Loss')\n# plt.plot(range(1, epochs + 1), val_losses, label='Validation Loss')\n# plt.xlabel('Epochs')\n# plt.ylabel('Loss')\n# plt.legend()\n# plt.title('Training and Validation Loss')\n\n# # Plotting validation accuracy\n# plt.subplot(1, 2, 2)\n# plt.plot(range(1, epochs + 1), val_accuracies, label='Validation Accuracy')\n# plt.xlabel('Epochs')\n# plt.ylabel('Accuracy (%)')\n# plt.legend()\n# plt.title('Validation Accuracy')\n\n# plt.show()\n","metadata":{"execution":{"iopub.status.busy":"2024-11-13T18:01:36.457614Z","iopub.execute_input":"2024-11-13T18:01:36.457985Z","iopub.status.idle":"2024-11-13T18:02:52.103983Z","shell.execute_reply.started":"2024-11-13T18:01:36.457952Z","shell.execute_reply":"2024-11-13T18:02:52.102672Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # Get unique values in the start_stop_trigger column\n# unique_triggers = sample_raw_ppg['start_stop_trigger'].unique()\n# unique_triggers_num = sample_raw_ppg['start_stop_trigger'].nunique()\n# # Print the unique start_stop_trigger values\n# print(\"Unique start_stop_triggers in PPG data:\", unique_triggers)\n# print(unique_triggers_num)","metadata":{"execution":{"iopub.status.busy":"2024-11-13T17:29:30.368297Z","iopub.execute_input":"2024-11-13T17:29:30.368995Z","iopub.status.idle":"2024-11-13T17:29:30.373354Z","shell.execute_reply.started":"2024-11-13T17:29:30.368944Z","shell.execute_reply":"2024-11-13T17:29:30.372288Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# print(sample_raw_gsr['gsr_value'])\n\n# gsr_10 = sample_raw_gsr['gsr_value']\n","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:31.670392Z","iopub.execute_input":"2024-11-10T09:07:31.671179Z","iopub.status.idle":"2024-11-10T09:07:31.679014Z","shell.execute_reply.started":"2024-11-10T09:07:31.671132Z","shell.execute_reply":"2024-11-10T09:07:31.678008Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# print(gsr_10)","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:31.680253Z","iopub.execute_input":"2024-11-10T09:07:31.681168Z","iopub.status.idle":"2024-11-10T09:07:31.689479Z","shell.execute_reply.started":"2024-11-10T09:07:31.681082Z","shell.execute_reply":"2024-11-10T09:07:31.688427Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# gsr_10.shape","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:31.690581Z","iopub.execute_input":"2024-11-10T09:07:31.691518Z","iopub.status.idle":"2024-11-10T09:07:31.698723Z","shell.execute_reply.started":"2024-11-10T09:07:31.691469Z","shell.execute_reply":"2024-11-10T09:07:31.697936Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # for ppg\n\n# # sample_raw_ppg = pd.read_csv(\n# #    '/kaggle/input/raw-ppg-gsr-data-for-emotion-recognition/raw_data_ppg_gsr/10/10/raw_ppg.csv', \n# #     names=['timestamp', 'ppg_value', 'extra_column'],  # Define expected columns\n# #     na_values=[''],  # Treat empty strings as NaN\n# #     skiprows=1  # Skip header if needed\n# # )\n# sample_raw_ppg = pd.read_csv(\n#    '/kaggle/input/raw_data_ppg_gsr/10/10/raw_ppg.csv', \n#     names=['timestamp', 'ppg_value', 'extra_column'],  # Define expected columns\n#     na_values=[''],  # Treat empty strings as NaN\n#     skiprows=1  # Skip header if needed\n# )\n# print(sample_raw_ppg['ppg_value'])\n# ppg_10 = sample_raw_ppg['ppg_value']","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:31.699707Z","iopub.execute_input":"2024-11-10T09:07:31.700053Z","iopub.status.idle":"2024-11-10T09:07:31.820494Z","shell.execute_reply.started":"2024-11-10T09:07:31.70002Z","shell.execute_reply":"2024-11-10T09:07:31.819387Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# ppg_10.shape","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:31.824436Z","iopub.execute_input":"2024-11-10T09:07:31.824745Z","iopub.status.idle":"2024-11-10T09:07:31.830657Z","shell.execute_reply.started":"2024-11-10T09:07:31.824713Z","shell.execute_reply":"2024-11-10T09:07:31.829838Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Organize data as shown below :\n\nX_train :\n\n| subject_id | sequence_id | ppg_series | gsr_series |\n|------------|-------------|------------|------------|\n| 1          | 1           | [...]      | [...]      |\n| 1          | 2           | [...]      | [...]      |\n| 1          | 3           | [...]      | [...]      |\n| 2          | 1           | [...]      | [...]      |\n| 2          | 2           | [...]      | [...]      |\n| 2          | 3           | [...]      | [...]      |\n\n\ny_train :\n\n| subject_id | sequence_id | arousal | valence |\n|------------|-------------|---------|---------|\n| 1          | 1           | 7       | 6       |\n| 1          | 2           | 9       | 8       |\n| 1          | 3           | 4       | 5       |\n| 2          | 1           | 6       | 2       |\n| 2          | 2           | 8       | 4       |\n| 2          | 3           | 5       | 6       |","metadata":{}},{"cell_type":"markdown","source":"# Updated features_target dataframe\n\n| subject_id | series_id | ppg_series | gsr_series | Target |\n|------------|-----------|------------|------------|--------|\n| 1          | 1         | [...]      | [...]      | AxVy   |\n| 1          | 2         | [...]      | [...]      | AxVy   |\n| 1          | 3         | [...]      | [...]      | AxVy   |\n| 2          | 1         | [...]      | [...]      | AxVy   |\n| 2          | 2         | [...]      | [...]      | AxVy   |\n| 2          | 3         | [...]      | [...]      | AxVy   |\n\n","metadata":{}},{"cell_type":"code","source":"# import os\n# import pandas as pd\n# import numpy as np\n\n# # Initialize an empty list to store data for each row\n# data = []\n\n# # Define the base directory\n# # base_dir = '/kaggle/input/raw-ppg-gsr-data-for-emotion-recognition/raw_data_ppg_gsr/'\n# base_dir = '/kaggle/input/raw_data_ppg_gsr/'\n\n# # Loop through each subject's subdirectory\n# for subject_folder in os.listdir(base_dir):\n#     subject_dir = os.path.join(base_dir, subject_folder, subject_folder)\n    \n#     if os.path.isdir(subject_dir):  # Check if it’s a directory\n#         subject_id = int(subject_folder)  # Extract subject_id from folder name\n        \n#         # Load raw_gsr.csv and raw_ppg.csv with specific column names\n#         raw_gsr_path = os.path.join(subject_dir, 'raw_gsr.csv')\n#         raw_ppg_path = os.path.join(subject_dir, 'raw_ppg.csv')\n        \n#         # Define column names and load the data, treating empty strings as NaN and skipping the header row\n#         raw_gsr = pd.read_csv(\n#             raw_gsr_path, \n#             names=['timestamp', 'gsr_value', 'extra_column'], \n#             na_values=[''], \n#             skiprows=1\n#         )['gsr_value']  # Select only the 'gsr_value' column\n\n#         raw_ppg = pd.read_csv(\n#             raw_ppg_path, \n#             names=['timestamp', 'ppg_value', 'extra_column'], \n#             na_values=[''], \n#             skiprows=1\n#         )['ppg_value']  # Select only the 'ppg_value' column\n        \n#         # Calculate downsampling factor\n#         gsr_downsample_factor = len(raw_gsr) // 6000\n#         ppg_downsample_factor = len(raw_ppg) // 6000\n        \n#         # Downsample by taking every nth data point\n#         downsampled_gsr = raw_gsr.iloc[::gsr_downsample_factor].head(6000)\n#         downsampled_ppg = raw_ppg.iloc[::ppg_downsample_factor].head(6000)\n        \n#         # Split the downsampled data into chunks of 200 for each row in the DataFrame\n#         gsr_chunks = np.array_split(downsampled_gsr.values.flatten(), 30)\n#         ppg_chunks = np.array_split(downsampled_ppg.values.flatten(), 30)\n        \n#         # Load Arousal_Valence.csv for target labels\n#         av_path = os.path.join(subject_dir, 'Arousal_Valence.csv')\n#         av_data = pd.read_csv(av_path)\n\n#         for series_id in range(1, 31):  # For each of the 30 sequences per subject\n#             # Extract the chunk of 200 values for gsr_series and ppg_series\n#             gsr_series = gsr_chunks[series_id - 1]\n#             ppg_series = ppg_chunks[series_id - 1]\n            \n#             # Create target label in \"AxVy\" format\n#             valence = int(av_data.iloc[series_id - 1, 1])  # Second column is Valence\n#             arousal = int(av_data.iloc[series_id - 1, 2])  # Third column is Arousal\n#             target = f\"A{arousal}V{valence}\"\n            \n#             # Append the data to the list\n#             data.append({\n#                 'subject_id': subject_id,\n#                 'series_id': series_id,\n#                 'ppg_series': ppg_series,\n#                 'gsr_series': gsr_series,\n#                 'target': target\n#             })\n\n# # Convert the list of dictionaries to a DataFrame\n# features_target = pd.DataFrame(data)\n\n# # Display the first few rows of the DataFrame\n# features_target.head()","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:31.832055Z","iopub.execute_input":"2024-11-10T09:07:31.832402Z","iopub.status.idle":"2024-11-10T09:07:40.501827Z","shell.execute_reply.started":"2024-11-10T09:07:31.83237Z","shell.execute_reply":"2024-11-10T09:07:40.500802Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # Function to categorize Arousal (A) and Valence (V)\n# def categorize_arousal_valence(target_value):\n#     # Extract the Arousal (Ax) and Valence (Vy) digits\n#     arousal_level = int(target_value[1])\n#     valence_level = int(target_value[3])\n    \n#     # Determine arousal label\n#     if 1 <= arousal_level <= 3:\n#         arousal_label = 'L'\n#     elif 4 <= arousal_level <= 6:\n#         arousal_label = 'M'\n#     elif 7 <= arousal_level <= 9:\n#         arousal_label = 'H'\n    \n#     # Determine valence label\n#     if 1 <= valence_level <= 3:\n#         valence_label = 'L'\n#     elif 4 <= valence_level <= 6:\n#         valence_label = 'M'\n#     elif 7 <= valence_level <= 9:\n#         valence_label = 'H'\n    \n#     # Create the grouped target label\n#     return f\"A{arousal_label}V{valence_label}\"\n\n# # Apply the function to each row in the DataFrame\n# features_target['grouped_target'] = features_target['target'].apply(categorize_arousal_valence)\n\n# # Display the updated DataFrame\n# print(features_target)","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:40.503023Z","iopub.execute_input":"2024-11-10T09:07:40.503368Z","iopub.status.idle":"2024-11-10T09:07:40.529477Z","shell.execute_reply.started":"2024-11-10T09:07:40.503334Z","shell.execute_reply":"2024-11-10T09:07:40.528458Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"features_target.shape","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:40.530897Z","iopub.execute_input":"2024-11-10T09:07:40.531575Z","iopub.status.idle":"2024-11-10T09:07:40.542167Z","shell.execute_reply.started":"2024-11-10T09:07:40.531526Z","shell.execute_reply":"2024-11-10T09:07:40.541037Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# List all unique subject_id values","metadata":{}},{"cell_type":"code","source":"unique_subject_ids = features_target['subject_id'].unique()\nprint(unique_subject_ids)\n","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:40.543396Z","iopub.execute_input":"2024-11-10T09:07:40.543776Z","iopub.status.idle":"2024-11-10T09:07:40.550729Z","shell.execute_reply.started":"2024-11-10T09:07:40.54374Z","shell.execute_reply":"2024-11-10T09:07:40.549601Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Count of unique subject_id values","metadata":{}},{"cell_type":"code","source":"unique_subject_count = features_target['subject_id'].nunique()\nprint(unique_subject_count)\n","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:40.551876Z","iopub.execute_input":"2024-11-10T09:07:40.552663Z","iopub.status.idle":"2024-11-10T09:07:40.558607Z","shell.execute_reply.started":"2024-11-10T09:07:40.552622Z","shell.execute_reply":"2024-11-10T09:07:40.557551Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# After preprocessing check that each subject id has the same number of sequence ids (value counts)","metadata":{}},{"cell_type":"code","source":"import os\nimport torch\nimport numpy as np\nimport pandas as pd\nfrom tqdm.auto import tqdm\n\nimport torch\nimport torch.autograd as autograd\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\n\nimport seaborn as sns\nfrom pylab import rcParams\nimport matplotlib.pyplot as plt\nfrom matplotlib import rc\nfrom matplotlib.ticker import MaxNLocator\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import LabelEncoder\n\nfrom multiprocessing import cpu_count\n\nfrom sklearn.metrics import classification_report , confusion_matrix","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:40.559781Z","iopub.execute_input":"2024-11-10T09:07:40.56013Z","iopub.status.idle":"2024-11-10T09:07:42.811689Z","shell.execute_reply.started":"2024-11-10T09:07:40.560088Z","shell.execute_reply":"2024-11-10T09:07:42.810684Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Preprocessing to convert string values of surfaces to integers for neural network","metadata":{}},{"cell_type":"code","source":"# USING NUMERIC VALUES OF AROUSAL & VALENCE AS IS\n\n# label_encoder = LabelEncoder()\n# encoded_labels = label_encoder.fit_transform(features_target.target)\n\n# # GROUPING NUMERIC VALUES BASED ON RANGES: 1-3, 4-6 AND 7-9\n# label_encoder = LabelEncoder()\n# encoded_labels = label_encoder.fit_transform(features_target.grouped_target)","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:42.81299Z","iopub.execute_input":"2024-11-10T09:07:42.813533Z","iopub.status.idle":"2024-11-10T09:07:42.819681Z","shell.execute_reply.started":"2024-11-10T09:07:42.813485Z","shell.execute_reply":"2024-11-10T09:07:42.81871Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"encoded_labels","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:42.820833Z","iopub.execute_input":"2024-11-10T09:07:42.82123Z","iopub.status.idle":"2024-11-10T09:07:42.834108Z","shell.execute_reply.started":"2024-11-10T09:07:42.821186Z","shell.execute_reply":"2024-11-10T09:07:42.832909Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\n#labels are stored in the classes_ property in label encoder\nlabel_encoder.classes_\n# to reverse transformation if and when needed","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:42.835268Z","iopub.execute_input":"2024-11-10T09:07:42.835663Z","iopub.status.idle":"2024-11-10T09:07:42.843395Z","shell.execute_reply.started":"2024-11-10T09:07:42.835617Z","shell.execute_reply":"2024-11-10T09:07:42.842415Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"features_target['label']=encoded_labels","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:42.844613Z","iopub.execute_input":"2024-11-10T09:07:42.844906Z","iopub.status.idle":"2024-11-10T09:07:42.851777Z","shell.execute_reply.started":"2024-11-10T09:07:42.84487Z","shell.execute_reply":"2024-11-10T09:07:42.85087Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"unique_labels_count = features_target['label'].nunique()\nprint(unique_labels_count)","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:42.852928Z","iopub.execute_input":"2024-11-10T09:07:42.853268Z","iopub.status.idle":"2024-11-10T09:07:42.86081Z","shell.execute_reply.started":"2024-11-10T09:07:42.853236Z","shell.execute_reply":"2024-11-10T09:07:42.859937Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"features_target.head()","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:42.861978Z","iopub.execute_input":"2024-11-10T09:07:42.862336Z","iopub.status.idle":"2024-11-10T09:07:42.888941Z","shell.execute_reply.started":"2024-11-10T09:07:42.862303Z","shell.execute_reply":"2024-11-10T09:07:42.888103Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# import pandas as pd\n# import torch\n# from sklearn.model_selection import train_test_split\n# from torch.utils.data import Dataset\n\n# # Step 1: Initialize an empty list to store sequences and labels\n# sequences = []\n\n# # Step 2: Iterate through each row in features_target DataFrame to create sequence_features and add to sequences list\n# for _, row in features_target.iterrows():\n#     # Extract the first 200 values from ppg_series and gsr_series, assuming they are stored as arrays\n#     sequence_features = pd.DataFrame({\n#         'ppg_series': row['ppg_series'][:200],\n#         'gsr_series': row['gsr_series'][:200]\n#     })\n    \n#     # Append (sequence_features, label) as a tuple to the sequences list\n#     sequences.append((sequence_features, row['label']))","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:42.890047Z","iopub.execute_input":"2024-11-10T09:07:42.890411Z","iopub.status.idle":"2024-11-10T09:07:43.424666Z","shell.execute_reply.started":"2024-11-10T09:07:42.890368Z","shell.execute_reply":"2024-11-10T09:07:43.423871Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"train_sequences, test_sequences = train_test_split(sequences, test_size=0.2)\n\nlen(train_sequences) , len(test_sequences)","metadata":{}},{"cell_type":"code","source":"# Step 3: Split into training and test sets\ntrain_sequences, test_sequences = train_test_split(sequences, test_size=0.2)\nlen(train_sequences), len(test_sequences)","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:43.425765Z","iopub.execute_input":"2024-11-10T09:07:43.426082Z","iopub.status.idle":"2024-11-10T09:07:43.433784Z","shell.execute_reply.started":"2024-11-10T09:07:43.426038Z","shell.execute_reply":"2024-11-10T09:07:43.432811Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # Dataset class\n# class MixedEmotionDataset(Dataset): \n#     def __init__(self, sequences):\n#         self.sequences = sequences\n        \n#     def __len__(self):\n#         return len(self.sequences)\n    \n#     def __getitem__(self, idx):\n#         sequence, label = self.sequences[idx]\n#         return dict(\n#             sequence=torch.Tensor(sequence.to_numpy()),  # Convert DataFrame to tensor\n#             label=torch.tensor(label).long()\n#         )","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:43.434858Z","iopub.execute_input":"2024-11-10T09:07:43.435199Z","iopub.status.idle":"2024-11-10T09:07:43.442404Z","shell.execute_reply.started":"2024-11-10T09:07:43.435166Z","shell.execute_reply":"2024-11-10T09:07:43.441361Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # MixedEmotionDataLoader for train, validation, and test DataLoaders\n# class MixedEmotionDataLoader:\n    \n#     def __init__(self, train_sequences, test_sequences, batch_size):\n#         self.train_sequences = train_sequences\n#         self.test_sequences = test_sequences\n#         self.batch_size = batch_size\n#         self.setup()\n        \n#     def setup(self):\n#         self.train_dataset = MixedEmotionDataset(self.train_sequences)\n#         self.test_dataset = MixedEmotionDataset(self.test_sequences)\n        \n#     def get_train_loader(self):\n#         return DataLoader(\n#             self.train_dataset,\n#             batch_size=self.batch_size,\n#             shuffle=True,\n#             num_workers=cpu_count()\n#         )\n    \n#     def get_val_loader(self):\n#         return DataLoader(\n#             self.test_dataset,\n#             batch_size=self.batch_size,\n#             shuffle=False,\n#             num_workers=cpu_count()\n#         )\n    \n#     def get_test_loader(self):\n#         return DataLoader(\n#             self.test_dataset,\n#             batch_size=self.batch_size,\n#             shuffle=False,\n#             num_workers=cpu_count()\n#         )\n","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:43.443577Z","iopub.execute_input":"2024-11-10T09:07:43.443882Z","iopub.status.idle":"2024-11-10T09:07:43.451987Z","shell.execute_reply.started":"2024-11-10T09:07:43.443852Z","shell.execute_reply":"2024-11-10T09:07:43.45112Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Model","metadata":{}},{"cell_type":"code","source":"# OLDER VERSION\n\n# class SequenceModel(nn.Module):\n#     # classification, number of hidden units, number of layers for LSTM\n    \n#     def __init__(self, n_features, n_classes , n_hidden=256, n_layers=3):\n#         super().__init__()\n        \n#         self.lstm = nn.LSTM(\n#         input_size = n_features,\n#         hidden_size=n_hidden,\n#         num_layers=n_layers,\n#         batch_first=True,\n#         dropout=0.75)\n        \n#         self.classifier = nn.Linear(n_hidden , n_classes)\n        \n#     def forward(self, x):\n#         self.lstm.flatten_parameters()\n#         _, (hidden, _) = self.lstm(x)\n        \n#         out = hidden[-1]\n        \n#         return self.classifier(out)\n    \n# NEWER VERSION\n\n\n# import torch\n# import torch.nn as nn\n\n# class ImprovedSequenceModel(nn.Module):\n#     def __init__(self, n_features, n_classes, n_hidden=512, n_layers=3):\n#         super().__init__()\n        \n#         # One-directional LSTM with lower dropout\n#         self.lstm = nn.LSTM(\n#             input_size=n_features,\n#             hidden_size=n_hidden,\n#             num_layers=n_layers,\n#             batch_first=True,\n#             dropout=0.3,\n#             bidirectional=False  # One-directional LSTM\n#         )\n        \n#         # Layer Normalization for LSTM outputs\n#         self.layer_norm = nn.LayerNorm(n_hidden)\n\n#         # Classifier Layer\n#         self.classifier = nn.Linear(n_hidden, n_classes)  # Adjust for one-directional output\n        \n#     def forward(self, x):\n#         self.lstm.flatten_parameters()\n        \n#         # Pass through LSTM\n#         output, (hidden, _) = self.lstm(x)\n        \n#         # Only use the last hidden state\n#         out = hidden[-1]  # Take the final hidden state only\n        \n#         # Apply layer normalization\n#         out = self.layer_norm(out)\n        \n#         return self.classifier(out)\n","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:43.453031Z","iopub.execute_input":"2024-11-10T09:07:43.453367Z","iopub.status.idle":"2024-11-10T09:07:43.464144Z","shell.execute_reply.started":"2024-11-10T09:07:43.453335Z","shell.execute_reply":"2024-11-10T09:07:43.463232Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# import torch\n# import torch.nn as nn\n# import torch.optim as optim\n# from torchmetrics import Accuracy\n# import matplotlib.pyplot as plt\n\n# class MixedEmotionPredictor(nn.Module): \n#     def __init__(self, n_features: int, n_classes: int):\n#         super(MixedEmotionPredictor, self).__init__()\n#         self.device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n# #         self.model = SequenceModel(n_features, n_classes).to(self.device)\n#         self.model = ImprovedSequenceModel(n_features, n_classes).to(self.device)\n\n#         self.criterion = nn.CrossEntropyLoss()\n#         self.accuracy_metric = Accuracy(task='multiclass', num_classes=n_classes).to(self.device)\n        \n#         # Initialize lists to store metrics for each epoch\n#         self.epoch_train_losses = []\n#         self.epoch_val_losses = []\n#         self.epoch_train_accuracies = []\n#         self.epoch_val_accuracies = []\n        \n#     def forward(self, x):\n#         return self.model(x)\n    \n#     def compute_loss_and_accuracy(self, outputs, labels):\n#         loss = self.criterion(outputs, labels)\n#         predictions = torch.argmax(outputs, dim=1)\n#         accuracy = self.accuracy_metric(predictions, labels)\n#         return loss, accuracy\n        \n#     def plot_metrics(self):\n#         num_epochs = min(len(self.epoch_train_losses), len(self.epoch_val_losses))\n#         epochs = range(1, num_epochs + 1)\n        \n#         train_losses = self.epoch_train_losses[:num_epochs]\n#         val_losses = self.epoch_val_losses[:num_epochs]\n#         train_accuracies = self.epoch_train_accuracies[:num_epochs]\n#         val_accuracies = self.epoch_val_accuracies[:num_epochs]\n        \n#         plt.figure(figsize=(12, 5))\n        \n#         # Plot Loss\n#         plt.subplot(1, 2, 1)\n#         plt.plot(epochs, train_losses, 'b', label='Training Loss')\n#         plt.plot(epochs, val_losses, 'r', label='Validation Loss')\n#         plt.title('Training and Validation Loss')\n#         plt.xlabel('Epochs')\n#         plt.ylabel('Loss')\n#         plt.legend()\n        \n#         # Plot Accuracy\n#         plt.subplot(1, 2, 2)\n#         plt.plot(epochs, train_accuracies, 'b', label='Training Accuracy')\n#         plt.plot(epochs, val_accuracies, 'r', label='Validation Accuracy')\n#         plt.title('Training and Validation Accuracy')\n#         plt.xlabel('Epochs')\n#         plt.ylabel('Accuracy')\n#         plt.legend()\n        \n#         plt.tight_layout()\n#         plt.show()\n    \n#     def train_model(self, train_loader, val_loader, num_epochs=10, lr=0.0001):\n#         optimizer = optim.Adam(self.parameters(), lr=lr)\n\n#         for epoch in range(num_epochs):\n#             # Training phase\n#             self.train()\n#             train_losses = []\n#             train_accuracies = []\n\n#             for batch in train_loader:\n#                 sequences = batch[\"sequence\"].to(self.device)\n#                 labels = batch[\"label\"].to(self.device)\n\n#                 optimizer.zero_grad()\n#                 outputs = self(sequences)\n#                 loss, accuracy = self.compute_loss_and_accuracy(outputs, labels)\n\n#                 loss.backward()\n#                 optimizer.step()\n\n#                 train_losses.append(loss.item())\n#                 train_accuracies.append(accuracy.item())\n\n#             avg_train_loss = sum(train_losses) / len(train_losses)\n#             avg_train_accuracy = sum(train_accuracies) / len(train_accuracies)\n\n#             # Validation phase\n#             self.eval()\n#             val_losses = []\n#             val_accuracies = []\n\n#             with torch.no_grad():\n#                 for batch in val_loader:\n#                     sequences = batch[\"sequence\"].to(self.device)\n#                     labels = batch[\"label\"].to(self.device)\n\n#                     outputs = self(sequences)\n#                     loss, accuracy = self.compute_loss_and_accuracy(outputs, labels)\n\n#                     val_losses.append(loss.item())\n#                     val_accuracies.append(accuracy.item())\n\n#             avg_val_loss = sum(val_losses) / len(val_losses)\n#             avg_val_accuracy = sum(val_accuracies) / len(val_accuracies)\n\n#             # Store epoch metrics for plotting\n#             self.epoch_train_losses.append(avg_train_loss)\n#             self.epoch_val_losses.append(avg_val_loss)\n#             self.epoch_train_accuracies.append(avg_train_accuracy)\n#             self.epoch_val_accuracies.append(avg_val_accuracy)\n\n#             # Print metrics\n#             print(f\"Epoch {epoch + 1}/{num_epochs}\")\n#             print(f\"  Training Loss: {avg_train_loss:.4f}, Training Accuracy: {avg_train_accuracy:.4f}\")\n#             print(f\"  Validation Loss: {avg_val_loss:.4f}, Validation Accuracy: {avg_val_accuracy:.4f}\")\n\n#         # Plot training and validation metrics\n#         self.plot_metrics()\n","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:43.465572Z","iopub.execute_input":"2024-11-10T09:07:43.466267Z","iopub.status.idle":"2024-11-10T09:07:44.423468Z","shell.execute_reply.started":"2024-11-10T09:07:43.466222Z","shell.execute_reply":"2024-11-10T09:07:44.422618Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\n# import torch\n# from torch.utils.tensorboard import SummaryWriter\n# import os\n\n# # Parameters\n# N_EPOCHS = 250\n# BATCH_SIZE = 64\n# CHECKPOINT_DIR = \"checkpoints\"\n# LOG_DIR = \"lightning_logs/surface\"\n# BEST_MODEL_PATH = os.path.join(CHECKPOINT_DIR, \"best-checkpoint.pth\")\n\n# # Ensure directories exist\n# os.makedirs(CHECKPOINT_DIR, exist_ok=True)\n\n# # Instantiate TensorBoard writer\n# writer = SummaryWriter(LOG_DIR)\n\n# # Function to save the model checkpoint\n# def save_checkpoint(model, optimizer, epoch, val_loss, best_val_loss):\n#     # Only save the model if the current val_loss is better than the best_val_loss\n#     if val_loss < best_val_loss:\n#         print(f\"Validation loss improved from {best_val_loss:.4f} to {val_loss:.4f}. Saving model...\")\n#         torch.save({\n#             'epoch': epoch,\n#             'model_state_dict': model.state_dict(),\n#             'optimizer_state_dict': optimizer.state_dict(),\n#             'val_loss': val_loss,\n#         }, BEST_MODEL_PATH)\n#         return val_loss\n#     return best_val_loss\n\n# # Training loop\n# def train(model, train_loader, val_loader, criterion, optimizer):\n#     best_val_loss = float('inf')\n\n#     for epoch in range(1, N_EPOCHS + 1):\n#         model.train()\n#         train_loss = 0.0\n#         train_correct = 0\n#         train_total = 0\n        \n#         # Training step\n#         for batch in train_loader:\n#             sequences, labels = batch[\"sequence\"].to(model.device), batch[\"label\"].to(model.device)\n#             optimizer.zero_grad()\n#             outputs = model(sequences)\n#             loss = criterion(outputs, labels)\n#             loss.backward()\n#             optimizer.step()\n            \n#             train_loss += loss.item() * sequences.size(0)\n#             train_correct += (outputs.argmax(1) == labels).sum().item()\n#             train_total += labels.size(0)\n        \n#         avg_train_loss = train_loss / train_total\n#         train_accuracy = train_correct / train_total\n        \n#         # Validation step\n#         model.eval()\n#         val_loss = 0.0\n#         val_correct = 0\n#         val_total = 0\n#         with torch.no_grad():\n#             for batch in val_loader:\n#                 sequences, labels = batch[\"sequence\"].to(model.device), batch[\"label\"].to(model.device)\n#                 outputs = model(sequences)\n#                 loss = criterion(outputs, labels)\n#                 val_loss += loss.item() * sequences.size(0)\n#                 val_correct += (outputs.argmax(1) == labels).sum().item()\n#                 val_total += labels.size(0)\n                \n#         avg_val_loss = val_loss / val_total\n#         val_accuracy = val_correct / val_total\n\n#         # Logging to TensorBoard\n#         writer.add_scalar(\"Loss/Train\", avg_train_loss, epoch)\n#         writer.add_scalar(\"Loss/Validation\", avg_val_loss, epoch)\n#         writer.add_scalar(\"Accuracy/Train\", train_accuracy, epoch)\n#         writer.add_scalar(\"Accuracy/Validation\", val_accuracy, epoch)\n\n#         # Print metrics\n#         print(f\"Epoch {epoch}/{N_EPOCHS}, \"\n#               f\"Train Loss: {avg_train_loss:.4f}, Train Accuracy: {train_accuracy:.4f}, \"\n#               f\"Val Loss: {avg_val_loss:.4f}, Val Accuracy: {val_accuracy:.4f}\")\n        \n#         # Save metrics to model for plotting\n#         model.epoch_train_losses.append(avg_train_loss)\n#         model.epoch_train_accuracies.append(train_accuracy)\n#         model.epoch_val_losses.append(avg_val_loss)\n#         model.epoch_val_accuracies.append(val_accuracy)\n        \n#         # Save checkpoint\n#         best_val_loss = save_checkpoint(model, optimizer, epoch, avg_val_loss, best_val_loss)\n\n#     writer.close()\n\n# # Example usage\n# device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n# n_features = 2  # using ppg_series and gsr_series features\n# n_classes = len(label_encoder.classes_)  # Replace with actual number of classes\n\n# # Initialize model, optimizer, and criterion\n# model = MixedEmotionPredictor(n_features=n_features, n_classes=n_classes).to(device)\n# optimizer = torch.optim.Adam(model.parameters(), lr=0.0001)\n# criterion = torch.nn.CrossEntropyLoss()\n\n# # Instantiate data loader\n# data_loader = MixedEmotionDataLoader(train_sequences, test_sequences, BATCH_SIZE)\n# train_loader = data_loader.get_train_loader()\n# val_loader = data_loader.get_val_loader()\n\n# # Train the model\n# train(model, train_loader, val_loader, criterion, optimizer)\n\n# # Plot metrics\n# model.plot_metrics()\n","metadata":{"execution":{"iopub.status.busy":"2024-11-10T09:07:44.425239Z","iopub.execute_input":"2024-11-10T09:07:44.425807Z","iopub.status.idle":"2024-11-10T09:28:36.487951Z","shell.execute_reply.started":"2024-11-10T09:07:44.425761Z","shell.execute_reply":"2024-11-10T09:28:36.486857Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}